home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / TIPS / SWTIP / SWTIP < prev   
Text File  |  1994-07-02  |  150KB  |  6,058 lines

  1. {!compiler.inc!}
  2. {$A+,B-,D-,E-,F-,G-,I+,L-,N-,O-,R-,S-,V+,X- Borland's Turbo Pascal}
  3. {$ifdef VER70}{$P-,Q-,T+}{$endif}
  4. {$M 65520,0,655360 memory}
  5. {$D+,L+ debugger}
  6. {$B+,R+,S+ run time}{$ifdef VER70}{*$Q+}{$endif}
  7. {!header.p!}
  8. {
  9.     Source code from the book
  10.     "Software Tools in Pascal", by
  11.     Brian W. Kernighan and P.J. Plauger
  12.     Addison-Wesley, 1981
  13.     ISBN 0-201-10342-7
  14.  
  15.   Copyright (c) 1981
  16.   By:  Bell Telephone Laboratories, Incorporated, and
  17.        Whitesmith's, Ltd.
  18. }
  19.  
  20. {!copyz.p!}
  21. { copyz -- copy input to output }
  22.   procedure COPYZ;
  23.   var
  24.     C : CHARACTER;
  25.   begin
  26.     while (GETC(C) <> ENDFILE) do
  27.       PUTC(C)
  28.   end;
  29.  
  30. {!copyprog.pas!}
  31. { complete copy -- to show one possible implementation }
  32. program COPYPROG(Input, Output);
  33. const
  34.   ENDFILE = -1;
  35.   NEWLINE = 10;                 { ASCII value }
  36. type
  37.   CHARACTER = -1..127;          { ASCII, plus ENDFILE }
  38.  
  39.   { getc -- get one character from standard input }
  40.   function GETC(var C : CHARACTER) : CHARACTER;
  41.   var
  42.     CH : Char;
  43.   begin
  44.     if (Eof) then
  45.       C := ENDFILE
  46.     else if (Eoln) then
  47.       begin
  48.         ReadLn;
  49.         C := NEWLINE;
  50.       end
  51.     else
  52.       begin
  53.         Read(CH);
  54.         C := Ord(CH);
  55.       end;
  56.     GETC := C;
  57.   end;
  58.  
  59.   { putc -- put one character on standard output }
  60.   procedure PUTC(C : CHARACTER);
  61.   begin
  62.     if (C = NEWLINE) then
  63.       WriteLn
  64.     else
  65.       Write(Chr(C));
  66.   end;
  67.  
  68.   { copyz -- copy input to output }
  69.   procedure COPYZ;
  70.   var
  71.     C : CHARACTER;
  72.   begin
  73.     while (GETC(C) <> ENDFILE) do
  74.       PUTC(C)
  75.   end;
  76.  
  77. begin                           { main program }
  78.   COPYZ
  79. end.
  80.  
  81. {!charcnt.p!}
  82.   { charcount -- count characters in standard input }
  83.   procedure CHARCOUNT;
  84.   var
  85.     NC : Integer;
  86.     C : CHARACTER;
  87.   begin
  88.     NC := 0;
  89.     while (GETC(C) <> ENDFILE) do
  90.       NC := NC+1;
  91.     PUTDEC(NC, 1);
  92.     PUTC(NEWLINE)
  93.   end;
  94.  
  95. {!linecnt.p!}
  96.   { linecount -- count lines in standard input }
  97.   procedure LINECOUNT;
  98.   var
  99.     N1 : Integer;
  100.     C : CHARACTER;
  101.   begin
  102.     N1 := 0;
  103.     while (GETC(C) <> ENDFILE) do
  104.       if (C = NEWLINE) then
  105.         N1 := N1+1;
  106.     PUTDEC(N1, 1);
  107.     PUTC(NEWLINE)
  108.   end;
  109.  
  110. {!wordcnt.p!}
  111.   { wordcount -- count words in standard input }
  112.   procedure WORDCOUNT;
  113.   var
  114.     NW : Integer;
  115.     C : CHARACTER;
  116.     INWORD : Boolean;
  117.   begin
  118.     NW := 0;
  119.     INWORD := False;
  120.     while (GETC(C) <> ENDFILE) do
  121.       if ((C = BLANK) or
  122.           (C = NEWLINE) or
  123.           (C = TAB)) then
  124.         INWORD := False
  125.       else if (not INWORD) then
  126.         begin
  127.           INWORD := True;
  128.           NW := NW+1;
  129.         end;
  130.     PUTDEC(NW, 1);
  131.     PUTC(NEWLINE)
  132.   end;
  133.  
  134. {!detab.p!}
  135.   { detab -- convert tabs to equivalent number of blanks }
  136.   procedure DETAB;
  137.   const
  138.     MAXLINE = 1000;             { or whatever }
  139.   type
  140.     TABTYPE = array[1..MAXLINE] of Boolean;
  141.   var
  142.     C : CHARACTER;
  143.     COL : Integer;
  144.     TABSTOPS : TABTYPE;
  145.  
  146. #include "tabpos.p"
  147. #include "settabs.p"
  148.   begin
  149.     SETTABS(TABSTOPS);          { set initial tab stops }
  150.     COL := 1;
  151.     while (GETC(C) <> ENDFILE) do
  152.       if (C = TAB) then
  153.         repeat
  154.           PUTC(BLANK);
  155.           COL := COL+1
  156.         until (TABPOS(COL, TABSTOPS))
  157.       else if (C = NEWLINE) then
  158.         begin
  159.           PUTC(NEWLINE);
  160.           COL := 1
  161.         end
  162.       else
  163.         begin
  164.           PUTC(C);
  165.           COL := COL+1
  166.         end
  167.   end;
  168.  
  169. {!tabpos.p!}
  170.   { tabpos -- return true if col is a tab stop }
  171.   function TABPOS(COL : Integer;
  172.                   var TABSTOPS : TABTYPE) : Boolean;
  173.   begin
  174.     if (COL > MAXLINE) then
  175.       TABPOS := True
  176.     else
  177.       TABPOS := TABSTOPS[COL]
  178.   end;
  179.  
  180. {!settabs.p!}
  181.   { settabs -- set initial tab stops }
  182.   procedure SETTABS(var TABSTOPS : TABTYPE);
  183.   const
  184.     TABSPACE = 8;               { 8 spaces per tab }
  185.   var
  186.     I : Integer;
  187.   begin
  188.     for I := 1 to MAXLINE do
  189.       TABSTOPS[I] := (I mod TABSPACE = 1)
  190.   end;
  191.  
  192. {!entab.p!}
  193.   { entab -- replace blanks by tabs and blanks }
  194.   procedure ENTAB;
  195.   const
  196.     MAXLINE = 1000;             { or whatever }
  197.   type
  198.     TABTYPE = array[1..MAXLINE] of Boolean;
  199.   var
  200.     C : CHARACTER;
  201.     COL, NEWCOL : Integer;
  202.     TABSTOPS : TABTYPE;
  203.  
  204. #include "tabpos.p"
  205. #include "settabs.p"
  206.   begin
  207.     SETTABS(TABSTOPS);
  208.     COL := 1;
  209.     repeat
  210.       NEWCOL := COL;
  211.       while (GETC(C) = BLANK) do { collect blanks }
  212.         begin
  213.           NEWCOL := NEWCOL+1;
  214.           if (TABPOS(NEWCOL, TABSTOPS)) then
  215.             begin
  216.               PUTC(TAB);
  217.               COL := NEWCOL
  218.             end
  219.         end;
  220.       while (COL < NEWCOL) do
  221.         begin
  222.           PUTC(BLANK);          { output leftover blanks }
  223.           COL := COL+1
  224.         end;
  225.       if (C <> ENDFILE) then
  226.         begin
  227.           PUTC(C);
  228.           if (C = NEWLINE) then
  229.             COL := 1
  230.           else
  231.             COL := COL+1
  232.         end
  233.     until (C = ENDFILE)
  234.   end;
  235.  
  236. {!overstrk.p!}
  237.   { overstrike -- convert backspaces into multiple lines }
  238.   procedure OVERSTRIKE;
  239.   const
  240.     SKIP = BLANK;
  241.     NOSKIP = PLUS;
  242.   var
  243.     C : CHARACTER;
  244.     COL, NEWCOL, I : Integer;
  245.   begin
  246.     COL := 1;
  247.     repeat
  248.       NEWCOL := COL;
  249.       while (GETC(C) = BACKSPACE) do { eat backspaces}
  250.         NEWCOL := MAX(NEWCOL-1, 1);
  251.       if (NEWCOL < COL) then
  252.         begin
  253.           PUTC(NEWLINE);        { start overstrike line }
  254.           PUTC(NOSKIP);
  255.           for I := 1 to NEWCOL-1 do
  256.             PUTC(BLANK);
  257.           COL := NEWCOL
  258.         end
  259.       else if ((COL = 1) and
  260.                (C <> ENDFILE)) then
  261.         PUTC(SKIP);             { normal line }
  262.       { else middle of line }
  263.       if (C <> ENDFILE) then
  264.         begin
  265.           PUTC(C);              { normal character}
  266.           if (C = NEWLINE) then
  267.             COL := 1
  268.           else
  269.             COL := COL+1
  270.         end
  271.     until (C = ENDFILE)
  272.   end;
  273.  
  274. {!max.p!}
  275.   { max -- compute maximum of two integers }
  276.   function MAX(X, Y : Integer) : Integer;
  277.   begin
  278.     if (X > Y) then
  279.       MAX := X
  280.     else
  281.       MAX := Y
  282.   end;
  283.  
  284. {!compress.p!}
  285.   { compress -- compress standard input }
  286.   procedure COMPRESS;
  287.   const
  288.     WARNING = TILDE;            { ~ }
  289.   var
  290.     C, LASTC : CHARACTER;
  291.     N : Integer;
  292.  
  293. #include "putrep.p"
  294.   begin
  295.     N := 1;
  296.     LASTC := GETC(LASTC);
  297.     while (LASTC <> ENDFILE) do
  298.       begin
  299.         if (GETC(C) = ENDFILE) then
  300.           begin
  301.             if ((N > 1) or
  302.                 (LASTC = WARNING)) then
  303.               PUTREP(N, LASTC)
  304.             else
  305.               PUTC(LASTC)
  306.           end
  307.         else if (C = LASTC) then
  308.           N := N+1
  309.         else if ((N > 1) or
  310.                  (LASTC = WARNING)) then
  311.           begin
  312.             PUTREP(N, LASTC);
  313.             N := 1
  314.           end
  315.         else
  316.           PUTC(LASTC);
  317.         LASTC := C
  318.       end
  319.   end;
  320.  
  321. {!putrep.p!}
  322.   { putrep -- put out representation of run of n 'c's }
  323.   procedure PUTREP(N : Integer;
  324.                    C : CHARACTER);
  325.   const
  326.     MAXREP = 26;                { assuming 'A'..'Z' }
  327.     THRESH = 4;
  328.   begin
  329.     while ((N >= THRESH) or
  330.            ((C = WARNING) and
  331.             (N > 0))) do
  332.       begin
  333.         PUTC(WARNING);
  334.         PUTC(MIN(N, MAXREP)-1+Ord('A'));
  335.         PUTC(C);
  336.         N := N-MAXREP
  337.       end;
  338.     for N := N downto 1 do
  339.       PUTC(C)
  340.   end;
  341.  
  342. {!min.p!}
  343.   { min -- compute minimum of two integers }
  344.   function MIN(X, Y : Integer) : Integer;
  345.   begin
  346.     if (X < Y) then
  347.       MIN := X
  348.     else
  349.       MIN := Y
  350.   end;
  351.  
  352. {!expand.p!}
  353.   { expand -- uncompress standard input }
  354.   procedure EXPAND;
  355.   const
  356.     WARNING = TILDE;            { ~ }
  357.   var
  358.     C : CHARACTER;
  359.     N : Integer;
  360.   begin
  361.     while (GETC(C) <> ENDFILE) do
  362.       if (C <> WARNING) then
  363.         PUTC(C)
  364.       else if (ISUPPER(GETC(C))) then
  365.         begin
  366.           N := C-Ord('A')+1;
  367.           if (GETC(C) <> ENDFILE) then
  368.             for N := N downto 1 do
  369.               PUTC(C)
  370.           else
  371.             begin
  372.               PUTC(WARNING);
  373.               PUTC(N-1+Ord('A'))
  374.             end
  375.         end
  376.       else
  377.         begin
  378.           PUTC(WARNING);
  379.           if (C <> ENDFILE) then
  380.             PUTC(C)
  381.         end
  382.   end;
  383.  
  384. {!isupper.p!}
  385.   { isupper -- true if c is upper case letter }
  386.   function ISUPPER(C : CHARACTER) : Boolean;
  387.   begin
  388.     ISUPPER := C in [Ord('A') ..Ord('Z')]
  389.   end;
  390.  
  391. {!echo.p!}
  392.   { echo -- echo command line arguments to output }
  393.   procedure ECHO;
  394.   var
  395.     I, J : Integer;
  396.     ARGSTR : STRINGZ;
  397.   begin
  398.     I := 1;
  399.     while (GETARG(I, ARGSTR, MAXSTR)) do
  400.       begin
  401.         if (I > 1) then PUTC(BLANK);
  402.         for J := 1 to LENGTHZ(ARGSTR) do
  403.           PUTC(ARGSTR[J]);
  404.         I := I+1
  405.       end;
  406.     if (I > 1) then
  407.       PUTC(NEWLINE)
  408.   end;
  409.  
  410. {!lengthz.p!}
  411.   { lengthz -- compute the length of stringz }
  412.   function LENGTHZ(var S : STRINGZ) : Integer;
  413.   var
  414.     N : Integer;
  415.   begin
  416.     N := 1;
  417.     while (S[N] <> ENDSTR) do
  418.       N := N+1;
  419.     LENGTHZ := N-1
  420.   end;
  421.  
  422. {!indexz.p!}
  423.   { indexz -- find position of character c in stringz s }
  424.   function INDEXZ(var S : STRINGZ;
  425.                   C : CHARACTER) : Integer;
  426.   var
  427.     I : Integer;
  428.   begin
  429.     I := 1;
  430.     while ((S[I] <> C) and
  431.            (S[I] <> ENDSTR)) do
  432.       I := I+1;
  433.     if (S[I] = ENDSTR) then
  434.       INDEXZ := 0
  435.     else
  436.       INDEXZ := I
  437.   end;
  438.  
  439. {!xindex.p!}
  440.   { xindex -- conditionally invert value from index }
  441.   function XINDEX(var INSET : STRINGZ;
  442.                   C : CHARACTER;
  443.                   ALLBUT : Boolean;
  444.                   LASTTO : Integer) : Integer;
  445.   begin
  446.     if (C = ENDFILE) then
  447.       XINDEX := 0
  448.     else if (not ALLBUT) then
  449.       XINDEX := INDEXZ(INSET, C)
  450.     else if (INDEXZ(INSET, C) > 0) then
  451.       XINDEX := 0
  452.     else
  453.       XINDEX := LASTTO+1
  454.   end;
  455.  
  456. {!translit.p!}
  457.   { translit -- map characters }
  458.   procedure TRANSLIT;
  459.   const
  460.     NEGATE = CARET;             { ^ }
  461.   var
  462.     ARG, FROMSET, TOSET : STRINGZ;
  463.     C : CHARACTER;
  464.     I, LASTTO : 0..MAXSTR;
  465.     ALLBUT, SQUASH : Boolean;
  466.  
  467. #include "makeset.p"
  468. #include "xindex.p"
  469.   begin
  470.     if (not GETARG(1, ARG, MAXSTR)) then
  471.       ERROR('usage: translit from to');
  472.     ALLBUT := (ARG[1] = NEGATE);
  473.     if (ALLBUT) then
  474.       I := 2
  475.     else
  476.       I := 1;
  477.     if (not MAKESET(ARG, I, FROMSET, MAXSTR)) then
  478.       ERROR('translit: "from" set too large');
  479.     if (not GETARG(2, ARG, MAXSTR)) then
  480.       TOSET[1] := ENDSTR
  481.     else if (not MAKESET(ARG, 1, TOSET, MAXSTR)) then
  482.       ERROR('translit: "to" set too large')
  483.     else if (LENGTHZ(FROMSET) < LENGTHZ(TOSET)) then
  484.       ERROR('translit: "from" shorter than "to"');
  485.  
  486.     LASTTO := LENGTHZ(TOSET);
  487.     SQUASH := (LENGTHZ(FROMSET) > LASTTO) or (ALLBUT);
  488.     repeat
  489.       I := XINDEX(FROMSET, GETC(C), ALLBUT, LASTTO);
  490.       if ((SQUASH) and
  491.           (I >= LASTTO) and
  492.           (LASTTO > 0)) then
  493.         begin
  494.           PUTC(TOSET[LASTTO]);
  495.           repeat
  496.             I := XINDEX(FROMSET, GETC(C), ALLBUT, LASTTO)
  497.           until (I < LASTTO)
  498.         end;
  499.       if (C <> ENDFILE) then
  500.         begin
  501.           if ((I > 0) and
  502.               (LASTTO > 0)) then { translate }
  503.             PUTC(TOSET[I])
  504.           else if (I = 0) then  { copy }
  505.             PUTC(C)
  506.             { else delete }
  507.         end
  508.     until (C = ENDFILE)
  509.   end;
  510.  
  511. {!makeset.p!}
  512.   { makeset -- make set from inset[k] in outset }
  513.   function MAKESET(var INSET : STRINGZ;
  514.                    K : Integer;
  515.                    var OUTSET : STRINGZ;
  516.                    MAXSET : Integer) : Boolean;
  517.   var
  518.     J : Integer;
  519.  
  520. #include "dodash.p"
  521.   begin
  522.     J := 1;
  523.     DODASH(ENDSTR, INSET, K, OUTSET, J, MAXSET);
  524.     MAKESET := ADDSTR(ENDSTR, OUTSET, J, MAXSET)
  525.   end;
  526.  
  527. {!addstr.p!}
  528.   { addstr -- put c in outset[j] if it fits, increment j }
  529.   function ADDSTR(C : CHARACTER;
  530.                   var OUTSET : STRINGZ;
  531.                   var J : Integer;
  532.                   MAXSET : Integer) : Boolean;
  533.   begin
  534.     if (J > MAXSET) then
  535.       ADDSTR := False
  536.     else
  537.       begin
  538.         OUTSET[J] := C;
  539.         J := J+1;
  540.         ADDSTR := True
  541.       end
  542.   end;
  543.  
  544. {!dodash.p!}
  545.   { dodash -- expand set at src[i] into dest[j], stop at delim }
  546.   procedure DODASH(DELIM : CHARACTER;
  547.                    var SRC : STRINGZ;
  548.                    var I : Integer;
  549.                    var DEST : STRINGZ;
  550.                    var J : Integer;
  551.                    MAXSET : Integer);
  552.   const
  553.     ESCAPE = ATSIGN;
  554.   var
  555.     K : Integer;
  556.     JUNK : Boolean;
  557.   begin
  558.     while ((SRC[I] <> DELIM) and
  559.            (SRC[I] <> ENDSTR)) do
  560.       begin
  561.         if (SRC[I] = ESCAPE) then
  562.           JUNK := ADDSTR(ESC(SRC, I), DEST, J, MAXSET)
  563.         else if (SRC[I] <> DASH) then
  564.           JUNK := ADDSTR(SRC[I], DEST, J, MAXSET)
  565.         else if ((J <= 1) or
  566.                  (SRC[I+1] = ENDSTR)) then
  567.           JUNK := ADDSTR(DASH, DEST, J, MAXSET) { literal - }
  568.         else if ((ISALPHANUM(SRC[I-1])) and
  569.                  (ISALPHANUM(SRC[I+1])) and
  570.                  (SRC[I-1] <= SRC[I+1])) then
  571.           begin
  572.             for K := SRC[I-1]+1 to SRC[I+1] do
  573.               JUNK := ADDSTR(K, DEST, J, MAXSET);
  574.             I := I+1
  575.           end
  576.         else
  577.           JUNK := ADDSTR(DASH, DEST, J, MAXSET);
  578.         I := I+1
  579.       end
  580.   end;
  581.  
  582. {!isalnum.p!}
  583.   { isalphanum -- true if c is letter or digit }
  584.   function ISALPHANUM(C : CHARACTER) : Boolean;
  585.   begin
  586.     ISALPHANUM := C in [Ord('a') ..Ord('z'),
  587.                   Ord('A') ..Ord('Z'),
  588.                   Ord('0') ..Ord('9')]
  589.   end;
  590.  
  591. {!esc.p!}
  592.   { esc -- map s[i] into escaped character, increment i }
  593.   function ESC(var S : STRINGZ;
  594.                var I : Integer) : CHARACTER;
  595.   const
  596.     ESCAPE = ATSIGN;            { @ }
  597.   begin
  598.     if (S[I] <> ESCAPE) then
  599.       ESC := S[I]
  600.     else if (S[I+1] = ENDSTR) then { @ not special at end }
  601.       ESC := ESCAPE
  602.     else
  603.       begin
  604.         I := I+1;
  605.         if (S[I] = Ord('n')) then
  606.           ESC := NEWLINE
  607.         else if (S[I] = Ord('t')) then
  608.           ESC := TAB
  609.         else
  610.           ESC := S[I]
  611.       end
  612.   end;
  613.  
  614. {!putdec.p!}
  615.   { putdec -- put decimal integer n in field width >= w }
  616.   procedure PUTDEC(N, W : Integer);
  617.   var
  618.     I, ND : Integer;
  619.     S : STRINGZ;
  620.   begin
  621.     ND := ITOC(N, S, 1);
  622.     for I := ND to W do
  623.       PUTC(BLANK);
  624.     for I := 1 to ND-1 do
  625.       PUTC(S[I])
  626.   end;
  627.  
  628. {!itoc.p!}
  629.   { itoc -- convert integer n to char stringz in s[i]... }
  630.   function ITOC(N : Integer;
  631.                 var S : STRINGZ;
  632.                 I : Integer) : Integer; { returns end of s }
  633.   begin
  634.     if (N < 0) then
  635.       begin
  636.         S[I] := Ord('-');
  637.         ITOC := ITOC(-N, S, I+1)
  638.       end
  639.     else
  640.       begin
  641.         if (N >= 10) then
  642.           I := ITOC(N div 10, S, I);
  643.         S[I] := N mod 10+Ord('0');
  644.         S[I+1] := ENDSTR;
  645.         ITOC := I+1
  646.       end
  647.   end;
  648.  
  649. {!ctoi.p!}
  650.   { ctoi -- convert stringz at s[i] to integer, increment i }
  651.   function CTOI(var S : STRINGZ;
  652.                 var I : Integer) : Integer;
  653.   var
  654.     N, SIGN : Integer;
  655.   begin
  656.     while ((S[I] = BLANK) or
  657.            (S[I] = TAB)) do
  658.       I := I+1;
  659.     if (S[I] = MINUS) then
  660.       SIGN := -1
  661.     else
  662.       SIGN := 1;
  663.     if ((S[I] = PLUS) or
  664.         (S[I] = MINUS)) then
  665.       I := I+1;
  666.     N := 0;
  667.     while (ISDIGIT(S[I])) do
  668.       begin
  669.         N := 10*N+S[I]-Ord('0');
  670.         I := I+1
  671.       end;
  672.     CTOI := SIGN*N
  673.   end;
  674.  
  675. {!isdigit.p!}
  676.   { isdigit -- true if c is a digit }
  677.   function ISDIGIT(C : CHARACTER) : Boolean;
  678.   begin
  679.     ISDIGIT := C in [Ord('0') ..Ord('9')]
  680.   end;
  681.  
  682. {!equal.p!}
  683.   { equal -- test two stringzs for equality }
  684.   function EQUAL(var STR1, STR2 : STRINGZ) : Boolean;
  685.   var
  686.     I : Integer;
  687.   begin
  688.     I := 1;
  689.     while ((STR1[I] = STR2[I]) and
  690.            (STR1[I] <> ENDSTR)) do
  691.       I := I+1;
  692.     EQUAL := (STR1[I] = STR2[I])
  693.   end;
  694.  
  695. {!compare1.p!}
  696.   { compare1 -- (simple version) compare two files for equality }
  697.   procedure COMPARE1;
  698.   var
  699.     LINE1, LINE2 : STRINGZ;
  700.     LINENO : Integer;
  701.     F1, F2 : Boolean;
  702.  
  703. #include "diffmsg.p"
  704.   begin
  705.     LINENO := 0;
  706.     repeat
  707.       LINENO := LINENO+1;
  708.       F1 := GETLINE(LINE1, INFILE1, MAXSTR);
  709.       F2 := GETLINE(LINE2, INFILE2, MAXSTR);
  710.       if (F1 and F2) then
  711.         if (not EQUAL(LINE1, LINE2)) then
  712.           DIFFMSG(LINENO, LINE1, LINE2)
  713.     until ((F1 = False) or
  714.            (F2 = False));
  715.     if (F2 and not F1) then
  716.       WriteLn('compare: end of file on file1')
  717.     else if (F1 and not F2) then
  718.       WriteLn('compare: end of file on file2')
  719.   end;
  720.  
  721. {!diffmsg.p!}
  722.   { diffmsg -- print line numbers and differing lines }
  723.   procedure DIFFMSG(N : Integer;
  724.                     var LINE1, LINE2 : STRINGZ);
  725.   begin
  726.     PUTDEC(N, 1);
  727.     PUTC(COLON);
  728.     PUTC(NEWLINE);
  729.     PUTSTR(LINE1, STDOUT);
  730.     PUTSTR(LINE2, STDOUT)
  731.   end;
  732.  
  733. {!compare.p!}
  734.   { compare -- compare two files for equality }
  735.   procedure COMPARE;
  736.   var
  737.     LINE1, LINE2 : STRINGZ;
  738.     ARG1, ARG2 : STRINGZ;
  739.     LINENO : Integer;
  740.     INFILE1, INFILE2 : FILEDESC;
  741.     F1, F2 : Boolean;
  742.  
  743. #include "diffmsg.p"
  744.   begin
  745.     if ((not GETARG(1, ARG1, MAXSTR)) or
  746.         (not GETARG(2, ARG2, MAXSTR))) then
  747.       ERROR('usage: compare file1 file2');
  748.     INFILE1 := MUSTOPEN(ARG1, IOREAD);
  749.     INFILE2 := MUSTOPEN(ARG2, IOREAD);
  750.     LINENO := 0;
  751.     repeat
  752.       LINENO := LINENO+1;
  753.       F1 := GETLINE(LINE1, INFILE1, MAXSTR);
  754.       F2 := GETLINE(LINE2, INFILE2, MAXSTR);
  755.       if (F1 and F2) then
  756.         if (not EQUAL(LINE1, LINE2)) then
  757.           DIFFMSG(LINENO, LINE1, LINE2)
  758.     until ((F1 = False) or
  759.            (F2 = False));
  760.     if (F2 and not F1) then
  761.       WriteLn('compare: end of file on file1')
  762.     else if (F1 and not F2) then
  763.       WriteLn('compare: end of file on file2')
  764.   end;
  765.  
  766. {!mustopen.p!}
  767.   { mustopen -- open file or die }
  768.   function MUSTOPEN(var NAME : STRINGZ;
  769.                     MODE : Integer) : FILEDESC;
  770.   var
  771.     FD : FILEDESC;
  772.   begin
  773.     FD := OPEN(NAME, MODE);
  774.     if (FD = IOERROR) then
  775.       begin
  776.         PUTSTR(NAME, STDERR);
  777.         ERROR(': can''t open file')
  778.       end;
  779.     MUSTOPEN := FD
  780.   end;
  781.  
  782. {!getword.p!}
  783.   { getword -- get word from s[i] into out }
  784.   function GETWORD(var S : STRINGZ;
  785.                    I : Integer;
  786.                    var OUT : STRINGZ) : Integer;
  787.   var
  788.     J : Integer;
  789.   begin
  790.     while (S[I] in [BLANK, TAB, NEWLINE]) do
  791.       I := I+1;
  792.     J := 1;
  793.     while (not(S[I] in [ENDSTR, BLANK, TAB, NEWLINE])) do
  794.       begin
  795.         OUT[J] := S[I];
  796.         I := I+1;
  797.         J := J+1
  798.       end;
  799.     OUT[J] := ENDSTR;
  800.     if (S[I] = ENDSTR) then
  801.       GETWORD := 0
  802.     else
  803.       GETWORD := I
  804.   end;
  805.  
  806. {!includez.p!}
  807.   { includez -- replace #include "file" by contents of file }
  808.   procedure INCLUDEZ;
  809.   var
  810.     INCL : STRINGZ;             { value is '#include' }
  811.  
  812. #include "finclude.p"
  813.   begin
  814.     { setstring(incl, '#include'); }
  815.     INCL[1] := Ord('#');
  816.     INCL[2] := Ord('i');
  817.     INCL[3] := Ord('n');
  818.     INCL[4] := Ord('c');
  819.     INCL[5] := Ord('l');
  820.     INCL[6] := Ord('u');
  821.     INCL[7] := Ord('d');
  822.     INCL[8] := Ord('e');
  823.     INCL[9] := ENDSTR;
  824.     FINCLUDE(STDIN)
  825.   end;
  826.  
  827. {!finclude.p!}
  828.   { finclude -- include file desc f }
  829.   procedure FINCLUDE(F : FILEDESC);
  830.   var
  831.     LINE, STRZ : STRINGZ;
  832.     LOC, I : Integer;
  833.     F1 : FILEDESC;
  834.  
  835. #include "getword.p"
  836.   begin
  837.     while (GETLINE(LINE, F, MAXSTR)) do
  838.       begin
  839.         LOC := GETWORD(LINE, 1, STRZ);
  840.         if (not EQUAL(STRZ, INCL)) then
  841.           PUTSTR(LINE, STDOUT)
  842.         else
  843.           begin
  844.             LOC := GETWORD(LINE, LOC, STRZ);
  845.             STRZ[LENGTHZ(STRZ)] := ENDSTR; { remove quotes }
  846.             for I := 1 to LENGTHZ(STRZ) do
  847.               STRZ[I] := STRZ[I+1];
  848.             F1 := MUSTOPEN(STRZ, IOREAD);
  849.             FINCLUDE(F1);
  850.             CLOSEZ(F1)
  851.           end
  852.       end
  853.   end;
  854.  
  855. {!concatz.p!}
  856.   { concatz -- concatenate files into standard output }
  857.   procedure CONCATZ;
  858.   var
  859.     I : Integer;
  860.     JUNK : Boolean;
  861.     FD : FILEDESC;
  862.     S : STRINGZ;
  863.   begin
  864.     for I := 1 to NARGS do
  865.       begin
  866.         JUNK := GETARG(I, S, MAXSTR);
  867.         FD := MUSTOPEN(S, IOREAD);
  868.         FCOPY(FD, STDOUT);
  869.         CLOSEZ(FD)
  870.       end
  871.   end;
  872.  
  873. {!fcopy.p!}
  874.   { fcopy -- copy file fin to file fout }
  875.   procedure FCOPY(FIN, FOUT : FILEDESC);
  876.   var
  877.     C : CHARACTER;
  878.   begin
  879.     while (GETCF(C, FIN) <> ENDFILE) do
  880.       PUTCF(C, FOUT)
  881.   end;
  882.  
  883. {!print1.p!}
  884.   { print1 -- print files with headings }
  885.   procedure PRINT1;
  886.   var
  887.     NAME : STRINGZ;
  888.     I : Integer;
  889.     FIN : FILEDESC;
  890.     JUNK : Boolean;
  891.  
  892. #include "fprint.p"
  893.   begin
  894.     for I := 1 to NARGS do
  895.       begin
  896.         JUNK := GETARG(I, NAME, MAXSTR);
  897.         FIN := MUSTOPEN(NAME, IOREAD);
  898.         FPRINT(NAME, FIN);
  899.         CLOSEZ(FIN)
  900.       end
  901.   end;
  902.  
  903. {!fprint.p!}
  904.   { fprint -- print file "name" from fin }
  905.   procedure FPRINT(var NAME : STRINGZ;
  906.                    FIN : FILEDESC);
  907.   const
  908.     MARGIN1 = 2;
  909.     MARGIN2 = 2;
  910.     BOTTOM = 64;
  911.     PAGELEN = 66;
  912.   var
  913.     LINE : STRINGZ;
  914.     LINENO, PAGENO : Integer;
  915.  
  916. #include "skip.p"
  917. #include "head.p"
  918.   begin
  919.     PAGENO := 1;
  920.     SKIP(MARGIN1);
  921.     HEAD(NAME, PAGENO);
  922.     SKIP(MARGIN2);
  923.     LINENO := MARGIN1+MARGIN2+1;
  924.     while (GETLINE(LINE, FIN, MAXSTR)) do
  925.       begin
  926.         if (LINENO = 0) then
  927.           begin
  928.             SKIP(MARGIN1);
  929.             PAGENO := PAGENO+1;
  930.             HEAD(NAME, PAGENO);
  931.             SKIP(MARGIN2);
  932.             LINENO := MARGIN1+MARGIN2+1
  933.           end;
  934.         PUTSTR(LINE, STDOUT);
  935.         LINENO := LINENO+1;
  936.         if (LINENO >= BOTTOM) then
  937.           begin
  938.             SKIP(PAGELEN-LINENO);
  939.             LINENO := 0
  940.           end
  941.       end;
  942.     if (LINENO > 0) then
  943.       SKIP(PAGELEN-LINENO)
  944.   end;
  945.  
  946. {!skip.p!}
  947.   { skip -- output n blank lines }
  948.   procedure SKIP(N : Integer);
  949.   var
  950.     I : Integer;
  951.   begin
  952.     for I := 1 to N do
  953.       PUTC(NEWLINE)
  954.   end;
  955.  
  956. {!head.p!}
  957.   { head -- print top of page header }
  958.   procedure HEAD(var NAME : STRINGZ;
  959.                  PAGENO : Integer);
  960.   var
  961.     PAGE : STRINGZ;             { set to ' Page ' }
  962.   begin
  963.     { setstring(page, ' Page '); }
  964.     PAGE[1] := Ord(' ');
  965.     PAGE[2] := Ord('P');
  966.     PAGE[3] := Ord('a');
  967.     PAGE[4] := Ord('g');
  968.     PAGE[5] := Ord('e');
  969.     PAGE[6] := Ord(' ');
  970.     PAGE[7] := ENDSTR;
  971.     PUTSTR(NAME, STDOUT);
  972.     PUTSTR(PAGE, STDOUT);
  973.     PUTDEC(PAGENO, 1);
  974.     PUTC(NEWLINE)
  975.   end;
  976.  
  977. {!print.p!}
  978.   { print -- (default input STDIN) print files with headings }
  979.   procedure PRINT;
  980.   var
  981.     NAME : STRINGZ;
  982.     NULL : STRINGZ;             { value '' }
  983.     I : Integer;
  984.     FIN : FILEDESC;
  985.     JUNK : Boolean;
  986.  
  987. #include "fprint.p"
  988.   begin
  989.     { setstring (null, ''); }
  990.     NULL[1] := ENDSTR;
  991.     if (NARGS = 0) then
  992.       FPRINT(NULL, STDIN)
  993.     else
  994.       for I := 1 to NARGS do
  995.         begin
  996.           JUNK := GETARG(I, NAME, MAXSTR);
  997.           FIN := MUSTOPEN(NAME, IOREAD);
  998.           FPRINT(NAME, FIN);
  999.           CLOSEZ(FIN)
  1000.         end
  1001.   end;
  1002.  
  1003. {!makecopy.p!}
  1004.   { makecopy -- copy one file to another }
  1005.   procedure MAKECOPY;
  1006.   var
  1007.     INNAME, OUTNAME : STRINGZ;
  1008.     FIN, FOUT : FILEDESC;
  1009.   begin
  1010.     if ((not GETARG(1, INNAME, MAXSTR)) or
  1011.         (not GETARG(2, OUTNAME, MAXSTR))) then
  1012.       ERROR('usage: makecopy old new');
  1013.     FIN := MUSTOPEN(INNAME, IOREAD);
  1014.     FOUT := MUSTCREATE(OUTNAME, IOWRITE);
  1015.     FCOPY(FIN, FOUT);
  1016.     CLOSEZ(FIN);
  1017.     CLOSEZ(FOUT)
  1018.   end;
  1019.  
  1020. {!mustcrea.p!}
  1021.   { mustcreate -- create file or die }
  1022.   function MUSTCREATE(var NAME : STRINGZ;
  1023.                       MODE : Integer) : FILEDESC;
  1024.   var
  1025.     FD : FILEDESC;
  1026.   begin
  1027.     FD := CREATE(NAME, MODE);
  1028.     if (FD = IOERROR) then
  1029.       begin
  1030.         PUTSTR(NAME, STDERR);
  1031.         ERROR(': can''t create file')
  1032.       end;
  1033.     MUSTCREATE := FD
  1034.   end;
  1035.  
  1036. {!help.p!}
  1037.   { help -- print diagnostic for archive }
  1038.   procedure HELP;
  1039.   begin
  1040.     ERROR('usage: archive -[cdptux] archname [files...]')
  1041.   end;
  1042.  
  1043. {!getfns.p!}
  1044.   { getfns -- get filenames into fname, look for duplicates }
  1045.   procedure GETFNS;
  1046.   var
  1047.     I, J : Integer;
  1048.     JUNK : Boolean;
  1049.   begin
  1050.     ERRCOUNT := 0;
  1051.     NFILES := NARGS-2;
  1052.     if (NFILES > MAXFILES) then
  1053.       ERROR('archive: too many file names');
  1054.     for I := 1 to NFILES do
  1055.       JUNK := GETARG(I+2, FNAME[I], MAXSTR);
  1056.     for I := 1 to NFILES do
  1057.       FSTAT[I] := False;
  1058.     for I := 1 to NFILES-1 do
  1059.       for J := I+1 to NFILES do
  1060.         if (EQUAL(FNAME[I], FNAME[J])) then
  1061.           begin
  1062.             PUTSTR(FNAME[I], STDERR);
  1063.             ERROR(': duplicate file name')
  1064.           end
  1065.   end;
  1066.  
  1067. {!update.p!}
  1068.   { update -- update existing files, add new ones at end }
  1069.   procedure UPDATE(var ANAME : STRINGZ;
  1070.                    CMD : CHARACTER);
  1071.   var
  1072.     I : Integer;
  1073.     AFD, TFD : FILEDESC;
  1074.   begin
  1075.     TFD := MUSTCREATE(ARCHTEMP, IOWRITE);
  1076.     if (CMD = Ord('u')) then
  1077.       begin
  1078.         AFD := MUSTOPEN(ANAME, IOREAD);
  1079.         REPLACE(AFD, TFD, Ord('u')); { update existing }
  1080.         CLOSEZ(AFD)
  1081.       end;
  1082.     for I := 1 to NFILES do
  1083.       if (FSTAT[I] = False) then
  1084.         begin
  1085.           ADDFILE(FNAME[I], TFD);
  1086.           FSTAT[I] := True
  1087.         end;
  1088.     CLOSEZ(TFD);
  1089.     if (ERRCOUNT = 0) then
  1090.       FMOVE(ARCHTEMP, ANAME)
  1091.     else
  1092.       WriteLn('fatal errors - archive not altered');
  1093.     REMOVE(ARCHTEMP)
  1094.   end;
  1095.  
  1096. {!fmove.p!}
  1097.   { fmove -- move file name1 to name2 }
  1098.   procedure FMOVE(var NAME1, NAME2 : STRINGZ);
  1099.   var
  1100.     FD1, FD2 : FILEDESC;
  1101.   begin
  1102.     FD1 := MUSTOPEN(NAME1, IOREAD);
  1103.     FD2 := MUSTCREATE(NAME2, IOWRITE);
  1104.     FCOPY(FD1, FD2);
  1105.     CLOSEZ(FD1);
  1106.     CLOSEZ(FD2);
  1107.   end;
  1108.  
  1109. {!addfile.p!}
  1110.   { addfile -- add file "name" to archive }
  1111.   procedure ADDFILE(var NAME : STRINGZ;
  1112.                     FD : FILEDESC);
  1113.   var
  1114.     HEAD : STRINGZ;
  1115.     NFD : FILEDESC;
  1116.  
  1117. #include "makehdr.p"
  1118.   begin
  1119.     NFD := OPEN(NAME, IOREAD);
  1120.     if (NFD = IOERROR) then
  1121.       begin
  1122.         PUTSTR(NAME, STDERR);
  1123.         MESSAGE(': can''t add');
  1124.         ERRCOUNT := ERRCOUNT+1
  1125.       end;
  1126.     if (ERRCOUNT = 0) then
  1127.       begin
  1128.         MAKEHDR(NAME, HEAD);
  1129.         PUTSTR(HEAD, FD);
  1130.         FCOPY(NFD, FD);
  1131.         CLOSEZ(NFD)
  1132.       end
  1133.   end;
  1134.  
  1135. {!makehdr.p!}
  1136.   { makehdr -- make header line for archive member }
  1137.   procedure MAKEHDR(var NAME, HEAD : STRINGZ);
  1138.   var
  1139.     I : Integer;
  1140.   begin
  1141.     SCOPY(ARCHHDR, 1, HEAD, 1);
  1142.     I := LENGTHZ(HEAD)+1;
  1143.     HEAD[I] := BLANK;
  1144.     SCOPY(NAME, 1, HEAD, I+1);
  1145.     I := LENGTHZ(HEAD)+1;
  1146.     HEAD[I] := BLANK;
  1147.     I := ITOC(FSIZE(NAME), HEAD, I+1);
  1148.     HEAD[I] := NEWLINE;
  1149.     HEAD[I+1] := ENDSTR
  1150.   end;
  1151.  
  1152. {!scopy.p!}
  1153.   { scopy -- copy string at src[i] to dest[j] }
  1154.   procedure SCOPY(var SRC : STRINGZ;
  1155.                   I : Integer;
  1156.                   var DEST : STRINGZ;
  1157.                   J : Integer);
  1158.   begin
  1159.     while (SRC[I] <> ENDSTR) do
  1160.       begin
  1161.         DEST[J] := SRC[I];
  1162.         I := I+1;
  1163.         J := J+1
  1164.       end;
  1165.     DEST[J] := ENDSTR;
  1166.   end;
  1167.  
  1168. {!fsize.p!}
  1169.   { fsize -- size of file in characters }
  1170.   function FSIZE(var NAME : STRINGZ) : Integer;
  1171.   var
  1172.     C : CHARACTER;
  1173.     FD : FILEDESC;
  1174.     N : Integer;
  1175.   begin
  1176.     N := 0;
  1177.     FD := MUSTOPEN(NAME, IOREAD);
  1178.     while (GETCF(C, FD) <> ENDFILE) do
  1179.       N := N+1;
  1180.     CLOSEZ(FD);
  1181.     FSIZE := N
  1182.   end;
  1183.  
  1184. {!table.p!}
  1185.   { table -- print table of archive contents }
  1186.   procedure TABLE(var ANAME : STRINGZ);
  1187.   var
  1188.     HEAD, NAME : STRINGZ;
  1189.     SIZE : Integer;
  1190.     AFD : FILEDESC;
  1191.  
  1192. #include "tprint.p"
  1193.   begin
  1194.     AFD := MUSTOPEN(ANAME, IOREAD);
  1195.     while (GETHDR(AFD, HEAD, NAME, SIZE)) do
  1196.       begin
  1197.         if (FILEARG(NAME)) then
  1198.           TPRINT(HEAD);
  1199.         FSKIP(AFD, SIZE)
  1200.       end;
  1201.     NOTFOUND
  1202.   end;
  1203.  
  1204. {!tprint.p!}
  1205.   { tprint -- print table entry for one member }
  1206.   procedure TPRINT(var BUF : STRINGZ);
  1207.   var
  1208.     I : Integer;
  1209.     TEMP : STRINGZ;
  1210.   begin
  1211.     I := GETWORD(BUF, 1, TEMP); { header }
  1212.     I := GETWORD(BUF, I, TEMP); { name }
  1213.     PUTSTR(TEMP, STDOUT);
  1214.     PUTC(BLANK);
  1215.     I := GETWORD(BUF, I, TEMP); { size }
  1216.     PUTSTR(TEMP, STDOUT);
  1217.     PUTC(NEWLINE)
  1218.   end;
  1219.  
  1220. {!gethdr.p!}
  1221.   { gethdr -- get header info from fd }
  1222.   function GETHDR(FD : FILEDESC;
  1223.                   var BUF, NAME : STRINGZ;
  1224.                   var SIZE : Integer) : Boolean;
  1225.   var
  1226.     TEMP : STRINGZ;
  1227.     I : Integer;
  1228.   begin
  1229.     if (GETLINE(BUF, FD, MAXSTR) = False) then
  1230.       GETHDR := False
  1231.     else
  1232.       begin
  1233.         I := GETWORD(BUF, 1, TEMP);
  1234.         if (not EQUAL(TEMP, ARCHHDR)) then
  1235.           ERROR('archive not in proper format');
  1236.         I := GETWORD(BUF, I, NAME);
  1237.         SIZE := CTOI(BUF, I);
  1238.         GETHDR := True
  1239.       end
  1240.   end;
  1241.  
  1242. {!fskip.p!}
  1243.   { fskip -- skip n characters on file fd }
  1244.   procedure FSKIP(FD : FILEDESC;
  1245.                   N : Integer);
  1246.   var
  1247.     C : CHARACTER;
  1248.     I : Integer;
  1249.   begin
  1250.     for I := 1 to N do
  1251.       if (GETCF(C, FD) = ENDFILE) then
  1252.         ERROR('archive: end of file in fskip')
  1253.   end;
  1254.  
  1255. {!filearg.p!}
  1256.   { filearg -- check if name matches argument list }
  1257.   function FILEARG(var NAME : STRINGZ) : Boolean;
  1258.   var
  1259.     I : Integer;
  1260.     FOUND : Boolean;
  1261.   begin
  1262.     if (NFILES <= 0) then
  1263.       FILEARG := True
  1264.     else
  1265.       begin
  1266.         FOUND := False;
  1267.         I := 1;
  1268.         while ((not FOUND) and
  1269.                (I <= NFILES)) do
  1270.           begin
  1271.             if (EQUAL(NAME, FNAME[I])) then
  1272.               begin
  1273.                 FSTAT[I] := True;
  1274.                 FOUND := True
  1275.               end;
  1276.             I := I+1
  1277.           end;
  1278.         FILEARG := FOUND
  1279.       end
  1280.   end;
  1281.  
  1282. {!notfound.p!}
  1283.   { notfound -- print "not found" warning }
  1284.   procedure NOTFOUND;
  1285.   var
  1286.     I : Integer;
  1287.   begin
  1288.     for I := 1 to NFILES do
  1289.       if (FSTAT[I] = False) then
  1290.         begin
  1291.           PUTSTR(FNAME[I], STDERR);
  1292.           WriteLn(': not in archive');
  1293.           ERRCOUNT := ERRCOUNT+1
  1294.         end
  1295.   end;
  1296.  
  1297. {!extract.p!}
  1298.   { extract -- extract files from archive }
  1299.   procedure EXTRACT(var ANAME : STRINGZ;
  1300.                     CMD : CHARACTER);
  1301.   var
  1302.     ENAME, INLINEZ : STRINGZ;
  1303.     AFD, EFD : FILEDESC;
  1304.     SIZE : Integer;
  1305.   begin
  1306.     AFD := MUSTOPEN(ANAME, IOREAD);
  1307.     if (CMD = Ord('p')) then
  1308.       EFD := STDOUT
  1309.     else                        { cmd is 'x' }
  1310.       EFD := IOERROR;
  1311.     while (GETHDR(AFD, INLINEZ, ENAME, SIZE)) do
  1312.       if (not FILEARG(ENAME)) then
  1313.         FSKIP(AFD, SIZE)
  1314.       else
  1315.         begin
  1316.           if (EFD <> STDOUT) then
  1317.             EFD := CREATE(ENAME, IOWRITE);
  1318.           if (EFD = IOERROR) then
  1319.             begin
  1320.               PUTSTR(ENAME, STDERR);
  1321.               WriteLn(': cant''t create');
  1322.               ERRCOUNT := ERRCOUNT+1;
  1323.               FSKIP(AFD, SIZE)
  1324.             end
  1325.           else
  1326.             begin
  1327.               ACOPY(AFD, EFD, SIZE);
  1328.               if (EFD <> STDOUT) then
  1329.                 CLOSEZ(EFD)
  1330.             end
  1331.         end;
  1332.     NOTFOUND
  1333.   end;
  1334.  
  1335. {!acopy.p!}
  1336.   { acopy -- copy n characters from fdi to fdo }
  1337.   procedure ACOPY(FDI, FDO : FILEDESC;
  1338.                   N : Integer);
  1339.   var
  1340.     C : CHARACTER;
  1341.     I : Integer;
  1342.   begin
  1343.     for I := 1 to N do
  1344.       if (GETCF(C, FDI) = ENDFILE) then
  1345.         ERROR('archive: end of file in acopy')
  1346.       else
  1347.         PUTCF(C, FDO)
  1348.   end;
  1349.  
  1350. {!deletez.p!}
  1351.   { deletez -- delete files from archive }
  1352.   procedure DELETEZ(var ANAME : STRINGZ);
  1353.   var
  1354.     AFD, TFD : FILEDESC;
  1355.   begin
  1356.     if (NFILES <= 0) then       { protect innocents }
  1357.       ERROR('archive: -d requires explicit file names');
  1358.     AFD := MUSTOPEN(ANAME, IOREAD);
  1359.     TFD := MUSTCREATE(ARCHTEMP, IOWRITE);
  1360.     REPLACE(AFD, TFD, Ord('d'));
  1361.     NOTFOUND;
  1362.     CLOSEZ(AFD);
  1363.     CLOSEZ(TFD);
  1364.     if (ERRCOUNT = 0) then
  1365.       FMOVE(ARCHTEMP, ANAME)
  1366.     else
  1367.       WriteLn('fatal errors - archive not altered');
  1368.     REMOVE(ARCHTEMP)
  1369.   end;
  1370.  
  1371. {!replace.p!}
  1372.   { replace -- replace or delete files }
  1373.   procedure REPLACE(AFD, TFD : FILEDESC;
  1374.                     CMD : Integer);
  1375.   var
  1376.     INLINEZ, UNAME : STRINGZ;
  1377.     SIZE : Integer;
  1378.   begin
  1379.     while (GETHDR(AFD, INLINEZ, UNAME, SIZE)) do
  1380.       if (FILEARG(UNAME)) then
  1381.         begin
  1382.           if (CMD = Ord('u')) then { add new one }
  1383.             ADDFILE(UNAME, TFD);
  1384.           FSKIP(AFD, SIZE)      { discard old one }
  1385.         end
  1386.       else
  1387.         begin
  1388.           PUTSTR(INLINEZ, TFD);
  1389.           ACOPY(AFD, TFD, SIZE)
  1390.         end
  1391.   end;
  1392.  
  1393. {!archive.p!}
  1394.   { archive -- file maintainer }
  1395.   procedure Archive;
  1396.   const
  1397.     MAXFILES = 100;             { or whatever }
  1398.   var
  1399.     ANAME : STRINGZ;            { archive name }
  1400.     CMD : STRINGZ;              { command type }
  1401.     FNAME : array[1..MAXFILES] of STRINGZ; { filename args }
  1402.     FSTAT : array[1..MAXFILES] of Boolean; { true=in archive }
  1403.     NFILES : Integer;           { number of filename arguments }
  1404.     ERRCOUNT : Integer;         { number of errors }
  1405.     ARCHTEMP : STRINGZ;         { temp file name 'artemp' }
  1406.     ARCHHDR : STRINGZ;          { header string '-h-' }
  1407.  
  1408. #include "archproc.p"
  1409.   begin
  1410.     INITARCH;
  1411.     if ((not GETARG(1, CMD, MAXSTR)) or
  1412.         (not GETARG(2, ANAME, MAXSTR))) then
  1413.       HELP;
  1414.     GETFNS;
  1415.     if ((LENGTHZ(CMD) <> 2) or
  1416.         (CMD[1] <> Ord('-'))) then
  1417.       HELP
  1418.     else if ((CMD[2] = Ord('c')) or
  1419.              (CMD[2] = Ord('u'))) then
  1420.       UPDATE(ANAME, CMD[2])
  1421.     else if (CMD[2] = Ord('t')) then
  1422.       TABLE(ANAME)
  1423.     else if ((CMD[2] = Ord('x')) or
  1424.              (CMD[2] = Ord('p'))) then
  1425.       EXTRACT(ANAME, CMD[2])
  1426.     else if (CMD[2] = Ord('d')) then
  1427.       DELETEZ(ANAME)
  1428.     else
  1429.       HELP
  1430.   end;
  1431.  
  1432. {!initarch.p!}
  1433.   { initarch -- initialize variables for archive }
  1434.   procedure INITARCH;
  1435.   begin
  1436.     { setstring (archtemp, 'artemp'); }
  1437.     ARCHTEMP[1] := Ord('a');
  1438.     ARCHTEMP[2] := Ord('r');
  1439.     ARCHTEMP[3] := Ord('t');
  1440.     ARCHTEMP[4] := Ord('e');
  1441.     ARCHTEMP[5] := Ord('m');
  1442.     ARCHTEMP[6] := Ord('p');
  1443.     ARCHTEMP[7] := ENDSTR;
  1444.     { setstring(archhdr, '-h-'); }
  1445.     ARCHHDR[1] := Ord('-');
  1446.     ARCHHDR[2] := Ord('h');
  1447.     ARCHHDR[3] := Ord('-');
  1448.     ARCHHDR[4] := ENDSTR;
  1449.   end;
  1450.  
  1451. {!archproc.p!}
  1452. #include "getword.p"
  1453. #include "gethdr.p"
  1454. #include "filearg.p"
  1455. #include "fskip.p"
  1456. #include "fmove.p"
  1457. #include "fsize.p"
  1458. #include "acopy.p"
  1459. #include "notfound.p"
  1460. #include "addfile.p"
  1461. #include "replace.p"
  1462. #include "help.p"
  1463. #include "getfns.p"
  1464. #include "update.p"
  1465. #include "table.p"
  1466. #include "extract.p"
  1467. #include "deletez.p"
  1468. #include "initarch.p"
  1469.  
  1470. {!bubble.p!}
  1471.   { bubble -- bubble sort v[1] ... v[n] increasing }
  1472.   procedure BUBBLE(var V : INTARRAY;
  1473.                    N : Integer);
  1474.   var
  1475.     I, J, K : Integer;
  1476.   begin
  1477.     for I := N downto 2 do
  1478.       for J := 1 to I-1 do
  1479.         if (V[J] > V[J+1]) then { compare}
  1480.           begin
  1481.             K := V[J];          { exchange }
  1482.             V[J] := V[J+1];
  1483.             V[J+1] := K
  1484.           end
  1485.   end;
  1486.  
  1487. {!shell1.p!}
  1488.   { shell -- Shell sort v[1]...v[n] increasing }
  1489.   procedure SHELL(var V : INTARRAY;
  1490.                   N : Integer);
  1491.   var
  1492.     GAP, I, J, JG, K : Integer;
  1493.   begin
  1494.     GAP := N div 2;
  1495.     while (GAP > 0) do
  1496.       begin
  1497.         for I := GAP+1 to N do
  1498.           begin
  1499.             J := I-GAP;
  1500.             while (J > 0) do
  1501.               begin
  1502.                 JG := J+GAP;
  1503.                 if (V[J] <= V[JG]) then { compare }
  1504.                   J := 0        { force loop termination }
  1505.                 else
  1506.                   begin
  1507.                     K := V[J];  { exchange }
  1508.                     V[J] := V[JG];
  1509.                     V[JG] := K
  1510.                   end;
  1511.                 J := J-GAP
  1512.               end
  1513.           end;
  1514.         GAP := GAP div 2
  1515.       end
  1516.   end;
  1517.  
  1518. {!sort1.p!}
  1519.   { sort -- external sort of text lines }
  1520.   procedure INMEMSORT;
  1521.   const
  1522.     MAXCHARS = 10000;           { maximum # of text characters }
  1523.     MAXLINES = 300;             { maximum # of lines }
  1524.   type
  1525.     CHARBUF = array[1..MAXCHARS] of CHARACTER;
  1526.     CHARPOS = 1..MAXCHARS;
  1527.     POSBUF = array[1..MAXLINES] of CHARPOS;
  1528.     POSZ = 0..MAXLINES;
  1529.   var
  1530.     LINEBUF : CHARBUF;
  1531.     LINEPOS : POSBUF;
  1532.     NLINES : POSZ;
  1533.  
  1534. #include "gtext.p"
  1535. #include "shell.p"
  1536. #include "ptext.p"
  1537.   begin
  1538.     if (GTEXT(LINEPOS, NLINES, LINEBUF, STDIN)) then
  1539.       begin
  1540.         SHELL(LINEPOS, NLINES, LINEBUF);
  1541.         PTEXT(LINEPOS, NLINES, LINEBUF, STDOUT)
  1542.       end
  1543.     else
  1544.       ERROR('sort: input too big to sort')
  1545.   end;
  1546.  
  1547. {!gtext.p!}
  1548.   { gtext -- get text lines into linebuf }
  1549.   function GTEXT(var LINEPOS : POSBUF;
  1550.                  var NLINES : POSZ;
  1551.                  var LINEBUF : CHARBUF;
  1552.                  INFILE : FILEDESC) : Boolean;
  1553.   var
  1554.     I, LEN, NEXTPOS : Integer;
  1555.     TEMP : STRINGZ;
  1556.     DONE : Boolean;
  1557.   begin
  1558.     NLINES := 0;
  1559.     NEXTPOS := 1;
  1560.     repeat
  1561.       DONE := (GETLINE(TEMP, INFILE, MAXSTR) = False);
  1562.       if (not DONE) then
  1563.         begin
  1564.           NLINES := NLINES+1;
  1565.           LINEPOS[NLINES] := NEXTPOS;
  1566.           LEN := LENGTHZ(TEMP);
  1567.           for I := 1 to LEN do
  1568.             LINEBUF[NEXTPOS+I-1] := TEMP[I];
  1569.           LINEBUF[NEXTPOS+LEN] := ENDSTR;
  1570.           NEXTPOS := NEXTPOS+LEN+1 { 1 for ENDSTR }
  1571.         end
  1572.     until ((DONE) or
  1573.            (NEXTPOS >= MAXCHARS-MAXSTR) or
  1574.            (NLINES >= MAXLINES));
  1575.     GTEXT := DONE
  1576.   end;
  1577.  
  1578. {!ptext.p!}
  1579.   { ptext -- output text lines from linebuf }
  1580.   procedure PTEXT(var LINEPOS : POSBUF;
  1581.                   NLINES : Integer;
  1582.                   var LINEBUF : CHARBUF;
  1583.                   OUTFILE : FILEDESC);
  1584.   var
  1585.     I, J : Integer;
  1586.   begin
  1587.     for I := 1 to NLINES do
  1588.       begin
  1589.         J := LINEPOS[I];
  1590.         while (LINEBUF[J] <> ENDSTR) do
  1591.           begin
  1592.             PUTCF(LINEBUF[J], OUTFILE);
  1593.             J := J+1
  1594.           end
  1595.       end
  1596.   end;
  1597.  
  1598. {!shell.p!}
  1599.   { shell -- ascending Shell sort for lines }
  1600.   procedure SHELL(var LINEPOS : POSBUF;
  1601.                   NLINES : Integer;
  1602.                   var LINEBUF : CHARBUF);
  1603.   var
  1604.     GAP, I, J, JG : Integer;
  1605.  
  1606. #include "cmp.p"
  1607. #include "exchange.p"
  1608.   begin
  1609.     GAP := NLINES div 2;
  1610.     while (GAP > 0) do
  1611.       begin
  1612.         for I := GAP+1 to NLINES do
  1613.           begin
  1614.             J := I-GAP;
  1615.             while (J > 0) do
  1616.               begin
  1617.                 JG := J+GAP;
  1618.                 if (CMP(LINEPOS[J], LINEPOS[JG], LINEBUF) <= 0) then
  1619.                   J := 0        { force loop termination }
  1620.                 else
  1621.                   begin
  1622.                     EXCHANGE(LINEPOS[J], LINEPOS[JG]);
  1623.                   end;
  1624.                 J := J-GAP
  1625.               end
  1626.           end;
  1627.         GAP := GAP div 2
  1628.       end
  1629.   end;
  1630.  
  1631. {!exchange.p!}
  1632.   { exchange -- exchange linebuf[lp1] with linebuf[lp2] }
  1633.   procedure EXCHANGE(var LP1, LP2 : CHARPOS);
  1634.   var
  1635.     TEMP : CHARPOS;
  1636.   begin
  1637.     TEMP := LP1;
  1638.     LP1 := LP2;
  1639.     LP2 := TEMP
  1640.   end;
  1641.  
  1642. {!cmp.p!}
  1643.   { cmp -- compare linebuf[i] with linebuf[j] }
  1644.   function CMP(I, J : CHARPOS;
  1645.                var LINEBUF : CHARBUF) : Integer;
  1646.   begin
  1647.     while ((LINEBUF[I] = LINEBUF[J]) and
  1648.            (LINEBUF[I] <> ENDSTR)) do
  1649.       begin
  1650.         I := I+1;
  1651.         J := J+1
  1652.       end;
  1653.     if (LINEBUF[I] = LINEBUF[J]) then
  1654.       CMP := 0
  1655.     else if (LINEBUF[I] = ENDSTR) then { 1st is shorter }
  1656.       CMP := -1
  1657.     else if (LINEBUF[J] = ENDSTR) then { 2nd is shorter }
  1658.       CMP := +1
  1659.     else if (LINEBUF[I] < LINEBUF[J]) then
  1660.       CMP := -1
  1661.     else
  1662.       CMP := +1
  1663.   end;
  1664.  
  1665. {!quick.p!}
  1666.   { quick -- quicksort for lines }
  1667.   procedure QUICK(var LINEPOS : POSBUF;
  1668.                   NLINE : POSZ;
  1669.                   var LINEBUF : CHARBUF);
  1670.  
  1671. #include "rquick.p"
  1672.   begin
  1673.     RQUICK(1, NLINES)
  1674.   end;
  1675.  
  1676. {!rquick.p!}
  1677.   { rquick -- recursive quicksort }
  1678.   { See Plauger's column in Computer Language, March 1987, page 16, }
  1679.   { and follow-up letters in May 1987, pages 9 & 11, for improvements. }
  1680.   procedure RQUICK(LOZ, HIZ : Integer);
  1681.   var
  1682.     I, J : Integer;
  1683.     PIVLINE : CHARPOS;
  1684.   begin
  1685.     if (LOZ < HIZ) then
  1686.       begin
  1687.         I := LOZ;
  1688.         J := HIZ;
  1689.         PIVLINE := LINEPOS[J];  { pivot line }
  1690.         repeat
  1691.           while (I < J)
  1692.           and (CMP(LINEPOS[I], PIVLINE, LINEBUF) <= 0) do
  1693.             I := I+1;
  1694.           while (J > I)
  1695.           and (CMP(LINEPOS[J], PIVLINE, LINEBUF) >= 0) do
  1696.             J := J-1;
  1697.           if (I < J) then       { out of order pair }
  1698.             EXCHANGE(LINEPOS[I], LINEPOS[J])
  1699.         until (I >= J);
  1700.         EXCHANGE(LINEPOS[I], LINEPOS[HIZ]); { move pivot to i }
  1701.         if (I-LOZ < HIZ-I) then
  1702.           begin
  1703.             RQUICK(LOZ, I-1);
  1704.             RQUICK(I+1, HIZ)
  1705.           end
  1706.         else
  1707.           begin
  1708.             RQUICK(I+1, HIZ);
  1709.             RQUICK(LOZ, I-1)
  1710.           end
  1711.       end
  1712.   end;
  1713.  
  1714. {!sort.p!}
  1715.   { sort -- external sort of text lines }
  1716.   procedure SORT;
  1717.   const
  1718.     MAXCHARS = 10000;           { maximum # of text characters }
  1719.     MAXLINES = 300;             { maximum # of lines }
  1720.     MERGEORDER = 5;
  1721.   type
  1722.     CHARPOS = 1..MAXCHARS;
  1723.     CHARBUF = array[1..MAXCHARS] of CHARACTER;
  1724.     POSBUF = array[1..MAXLINES] of CHARPOS;
  1725.     POSZ = 0..MAXLINES;
  1726.     FDBUF = array[1..MERGEORDER] of FILEDESC;
  1727.   var
  1728.     LINEBUF : CHARBUF;
  1729.     LINEPOS : POSBUF;
  1730.     NLINES : POSZ;
  1731.     INFILE : FDBUF;
  1732.     OUTFILE : FILEDESC;
  1733.     HIGHZ, LOWZ, LIM : Integer;
  1734.     DONE : Boolean;
  1735.     NAME : STRINGZ;
  1736.  
  1737. #include "sortproc.p"
  1738.   begin
  1739.     HIGHZ := 0;
  1740.     repeat                      { initial formation of runs }
  1741.       DONE := GTEXT(LINEPOS, NLINES, LINEBUF, STDIN);
  1742.       QUICK(LINEPOS, NLINES, LINEBUF);
  1743.       HIGHZ := HIGHZ+1;
  1744.       OUTFILE := MAKEFILE(HIGHZ);
  1745.       PTEXT(LINEPOS, NLINES, LINEBUF, OUTFILE);
  1746.       CLOSEZ(OUTFILE)
  1747.     until (DONE);
  1748.     LOWZ := 1;
  1749.     while (LOWZ < HIGHZ) do
  1750.       begin                     { merge runs }
  1751.         LIM := MIN(LOWZ+MERGEORDER-1, HIGHZ);
  1752.         GOPEN(INFILE, LOWZ, LIM);
  1753.         HIGHZ := HIGHZ+1;
  1754.         OUTFILE := MAKEFILE(HIGHZ);
  1755.         MERGE(INFILE, LIM-LOWZ+1, OUTFILE);
  1756.         CLOSEZ(OUTFILE);
  1757.         GREMOVE(INFILE, LOWZ, LIM);
  1758.         LOWZ := LOWZ+MERGEORDER
  1759.       end;
  1760.     GNAME(HIGHZ, NAME);         { final cleanup }
  1761.     OUTFILE := OPEN(NAME, IOREAD);
  1762.     FCOPY(OUTFILE, STDOUT);
  1763.     CLOSEZ(OUTFILE);
  1764.     REMOVE(NAME)
  1765.   end;
  1766.  
  1767.  
  1768. {!sortproc.p!}
  1769.   { sortproc -- procedures for sort }
  1770.  
  1771. #include "cmp.p"
  1772. #include "exchange.p"
  1773. #include "gtext.p"
  1774. #include "ptext.p"
  1775. #include "quick.p"
  1776. #include "gname.p"
  1777. #include "makefile.p"
  1778. #include "gopen.p"
  1779. #include "merge.p"
  1780. #include "gremove.p"
  1781.  
  1782. {!makefile.p!}
  1783.   { makefile -- make new file for number n }
  1784.   function MAKEFILE(N : Integer) : FILEDESC;
  1785.   var
  1786.     NAME : STRINGZ;
  1787.   begin
  1788.     GNAME(N, NAME);
  1789.     MAKEFILE := MUSTCREATE(NAME, IOWRITE)
  1790.   end;
  1791.  
  1792. {!gname.p!}
  1793.   { gname -- generate unique name for file id n }
  1794.   procedure GNAME(N : Integer;
  1795.                   var NAME : STRINGZ);
  1796.   var
  1797.     JUNK : Integer;
  1798.   begin
  1799.     { setstring(name, 'stemp'); }
  1800.     NAME[1] := Ord('s');
  1801.     NAME[2] := Ord('t');
  1802.     NAME[3] := Ord('e');
  1803.     NAME[4] := Ord('m');
  1804.     NAME[5] := Ord('p');
  1805.     NAME[6] := ENDSTR;
  1806.     JUNK := ITOC(N, NAME, LENGTHZ(NAME)+1)
  1807.   end;
  1808.  
  1809. {!gopen.p!}
  1810.   { gopen -- open group of files f1 ... f2 }
  1811.   procedure GOPEN(var INFILE : FDBUF;
  1812.                   F1, F2 : Integer);
  1813.   var
  1814.     NAME : STRINGZ;
  1815.     I : 1..MERGEORDER;
  1816.   begin
  1817.     for I := 1 to F2-F1+1 do
  1818.       begin
  1819.         GNAME(F1+I-1, NAME);
  1820.         INFILE[I] := MUSTOPEN(NAME, IOREAD)
  1821.       end
  1822.   end;
  1823.  
  1824. {!gremove.p!}
  1825.   { gremove -- remove group of files f1 ... f2 }
  1826.   procedure GREMOVE(var INFILE : FDBUF;
  1827.                     F1, F2 : Integer);
  1828.   var
  1829.     NAME : STRINGZ;
  1830.     I : 1..MERGEORDER;
  1831.   begin
  1832.     for I := 1 to F2-F1+1 do
  1833.       begin
  1834.         CLOSEZ(INFILE[I]);
  1835.         GNAME(F1+I-1, NAME);
  1836.         REMOVE(NAME)
  1837.       end
  1838.   end;
  1839.  
  1840. {!merge.p!}
  1841.   { merge -- merge infile[1] ... infile [nf] onto outfile }
  1842.   procedure MERGE(var INFILE : FDBUF;
  1843.                   NF : Integer;
  1844.                   OUTFILE : FILEDESC);
  1845.   var
  1846.     I, J : Integer;
  1847.     LBP : CHARPOS;
  1848.     TEMP : STRINGZ;
  1849.  
  1850. #include "reheap.p"
  1851. #include "sccopy.p"
  1852. #include "cscopy.p"
  1853.   begin
  1854.     J := 0;
  1855.     for I := 1 to NF do
  1856.       if (GETLINE(TEMP, INFILE[I], MAXSTR)) then
  1857.         begin
  1858.           LBP := (I-1)*MAXSTR+1; { room for longest }
  1859.           SCCOPY(TEMP, LINEBUF, LBP);
  1860.           LINEPOS[I] := LBP;
  1861.           J := J+1
  1862.         end;
  1863.     NF := J;
  1864.     QUICK(LINEPOS, NF, LINEBUF); { make initial heap }
  1865.     while (NF > 0) do
  1866.       begin
  1867.         LBP := LINEPOS[1];      { lowest line }
  1868.         CSCOPY(LINEBUF, LBP, TEMP);
  1869.         PUTSTR(TEMP, OUTFILE);
  1870.         I := LBP div MAXSTR+1;  { compute file index }
  1871.         if (GETLINE(TEMP, INFILE[I], MAXSTR)) then
  1872.           SCCOPY(TEMP, LINEBUF, LBP)
  1873.         else
  1874.           begin                 { one less input file }
  1875.             LINEPOS[1] := LINEPOS[NF];
  1876.             NF := NF-1
  1877.           end;
  1878.         REHEAP(LINEPOS, NF, LINEBUF)
  1879.       end
  1880.   end;
  1881.  
  1882. {!sccopy.p!}
  1883.   { sccopy -- copy string s into cb[i]... }
  1884.   procedure SCCOPY(var S : STRINGZ;
  1885.                    var CB : CHARBUF;
  1886.                    I : CHARPOS);
  1887.   var
  1888.     J : Integer;
  1889.   begin
  1890.     J := 1;
  1891.     while (S[J] <> ENDSTR) do
  1892.       begin
  1893.         CB[I] := S[J];
  1894.         J := J+1;
  1895.         I := I+1
  1896.       end;
  1897.     CB[I] := ENDSTR
  1898.   end;
  1899.  
  1900. {!cscopy.p!}
  1901.   { cscopy -- copy cs[i]... to string s }
  1902.   procedure CSCOPY(var CB : CHARBUF;
  1903.                    I : CHARPOS;
  1904.                    var S : STRINGZ);
  1905.   var
  1906.     J : Integer;
  1907.   begin
  1908.     J := 1;
  1909.     while (CB[I] <> ENDSTR) do
  1910.       begin
  1911.         S[J] := CB[I];
  1912.         I := I+1;
  1913.         J := J+1
  1914.       end;
  1915.     S[J] := ENDSTR
  1916.   end;
  1917.  
  1918. {!reheap.p!}
  1919.   { reheap -- put linebuf[linepos[i]] in proper place in heap }
  1920.   procedure REHEAP(var LINEPOS : POSBUF;
  1921.                    NF : POSZ;
  1922.                    var LINEBUF : CHARBUF);
  1923.   var
  1924.     I, J : Integer;
  1925.   begin
  1926.     I := 1;
  1927.     J := 2*I;
  1928.     while (J <= NF) do
  1929.       begin
  1930.         if (J < NF) then        { find smaller child }
  1931.           if (CMP(LINEPOS[J], LINEPOS[J+1], LINEBUF) > 0) then
  1932.             J := J+1;
  1933.         if (CMP(LINEPOS[I], LINEPOS[J], LINEBUF) <= 0) then
  1934.           I := NF               { proper position found; terminate loop }
  1935.         else
  1936.           EXCHANGE(LINEPOS[I], LINEPOS[J]); { percolate }
  1937.         I := J;
  1938.         J := 2*I
  1939.       end
  1940.   end;
  1941.  
  1942. {!unique.p!}
  1943.   { unique -- remove adjacent duplicate lines }
  1944.   procedure UNIQUE;
  1945.   var
  1946.     BUF : array[0..1] of STRINGZ;
  1947.     CUR : 0..1;
  1948.   begin
  1949.     CUR := 1;
  1950.     BUF[1-CUR][1] := ENDSTR;
  1951.     while (GETLINE(BUF[CUR], STDIN, MAXSTR)) do
  1952.       if (not EQUAL(BUF[CUR], BUF[1-CUR])) then
  1953.         begin
  1954.           PUTSTR(BUF[CUR], STDOUT);
  1955.           CUR := 1-CUR
  1956.         end
  1957.   end;
  1958.  
  1959. {!kwic.p!}
  1960.   { kwic -- make keyword in context index }
  1961.   procedure KWIC;
  1962.   const
  1963.     FOLD = DOLLAR;
  1964.   var
  1965.     BUF : STRINGZ;
  1966.  
  1967. #include "putrot.p"
  1968.   begin
  1969.     while (GETLINE(BUF, STDIN, MAXSTR)) do
  1970.       PUTROT(BUF)
  1971.   end;
  1972.  
  1973. {!putrot.p!}
  1974.   { putrot -- create lines with keyword at front }
  1975.   procedure PUTROT(var BUF : STRINGZ);
  1976.   var
  1977.     I : Integer;
  1978.  
  1979. #include "rotate.p"
  1980.   begin
  1981.     I := 1;
  1982.     while ((BUF[I] <> NEWLINE) and
  1983.            (BUF[I] <> ENDSTR)) do
  1984.       begin
  1985.         if (ISALPHANUM(BUF[I])) then
  1986.           begin
  1987.             ROTATE(BUF, I);     { token starts at "i" }
  1988.             repeat
  1989.               I := I+1
  1990.             until (not ISALPHANUM(BUF[I]))
  1991.           end;
  1992.         I := I+1
  1993.       end
  1994.   end;
  1995.  
  1996. {!rotate.p!}
  1997.   { rotate -- output rotated line }
  1998.   procedure ROTATE(var BUF : STRINGZ;
  1999.                    N : Integer);
  2000.   var
  2001.     I : Integer;
  2002.   begin
  2003.     I := N;
  2004.     while ((BUF[I] <> NEWLINE) and
  2005.            (BUF[I] <> ENDSTR)) do
  2006.       begin
  2007.         PUTC(BUF[I]);
  2008.         I := I+1
  2009.       end;
  2010.     PUTC(FOLD);
  2011.     for I := 1 to N-1 do
  2012.       PUTC(BUF[I]);
  2013.     PUTC(NEWLINE)
  2014.   end;
  2015.  
  2016. {!unrotate.p!}
  2017.   { unrotate -- unrotate lines rotated by kwic }
  2018.   procedure UNROTATE;
  2019.   const
  2020.     MAXOUT = 80;
  2021.     MIDDLE = 40;
  2022.     FOLD = DOLLAR;
  2023.   var
  2024.     INBUF, OUTBUF : STRINGZ;
  2025.     I, J, F : Integer;
  2026.   begin
  2027.     while (GETLINE(INBUF, STDIN, MAXSTR)) do
  2028.       begin
  2029.         for I := 1 to MAXOUT-1 do
  2030.           OUTBUF[I] := BLANK;
  2031.         F := INDEXZ(INBUF, FOLD);
  2032.         J := MIDDLE-1;
  2033.         for I := LENGTHZ(INBUF)-1 downto F+1 do
  2034.           begin
  2035.             OUTBUF[J] := INBUF[I];
  2036.             J := J-1;
  2037.             if (J <= 0) then
  2038.               J := MAXOUT-1
  2039.           end;
  2040.         J := MIDDLE+1;
  2041.         for I := 1 to F-1 do
  2042.           begin
  2043.             OUTBUF[J] := INBUF[I];
  2044.             J := J mod (MAXOUT-1)+1
  2045.           end;
  2046.         for J := 1 to MAXOUT-1 do
  2047.           if (OUTBUF[J] <> BLANK) then
  2048.             I := J;
  2049.         OUTBUF[I+1] := ENDSTR;
  2050.         PUTSTR(OUTBUF, STDOUT);
  2051.         PUTC(NEWLINE)
  2052.       end
  2053.   end;
  2054.  
  2055. {!find.p!}
  2056.   { find -- find patterns in text }
  2057.   procedure FIND;
  2058.  
  2059. #include "findcons.p"
  2060.   var
  2061.     ARG, LIN, PAT : STRINGZ;
  2062.  
  2063. #include "getpat.p"
  2064. #include "match.p"
  2065.   begin
  2066.     if (not GETARG(1, ARG, MAXSTR)) then
  2067.       ERROR('usage: find pattern');
  2068.     if (not GETPAT(ARG, PAT)) then
  2069.       ERROR('find: illegal pattern');
  2070.     while (GETLINE(LIN, STDIN, MAXSTR)) do
  2071.       if (MATCH(LIN, PAT)) then
  2072.         PUTSTR(LIN, STDOUT)
  2073.   end;
  2074.  
  2075. {!match.p!}
  2076.   { match -- find match anywhere on line }
  2077.   function MATCH(var LINE, PAT : STRINGZ) : Boolean;
  2078.   var
  2079.     I, POSZ : Integer;
  2080.  
  2081. #include "amatch.p"
  2082.   begin
  2083.     POSZ := 0;
  2084.     I := 1;
  2085.     while ((LIN[I] <> ENDSTR) and
  2086.            (POSZ = 0)) do
  2087.       begin
  2088.         POSZ := AMATCH(LIN, I, PAT, 1);
  2089.         I := I+1
  2090.       end;
  2091.     MATCH := (POSZ > 0)
  2092.   end;
  2093.  
  2094. {!amatch1.p!}
  2095.   { amatch -- with no metacharacters }
  2096.   function AMATCH(var LIN : STRINGZ;
  2097.                   I : Integer;
  2098.                   var PAT : STRINGZ;
  2099.                   J : Integer) : Integer;
  2100.   begin
  2101.     while (PAT[J] <> ENDSTR) do
  2102.       if (LIN[I] <> PAT[J]) then
  2103.         I := 0                  { no match }
  2104.       else
  2105.         begin
  2106.           I := I+1;
  2107.           J := J+1
  2108.         end;
  2109.     AMATCH := I
  2110.   end;
  2111.  
  2112. {!amatch2.p!}
  2113.   { amatch -- with some metacharacters }
  2114.   function AMATCH(var LIN : STRINGZ;
  2115.                   I : Integer;
  2116.                   var PAT : STRINGZ;
  2117.                   J : Integer) : Integer;
  2118.  
  2119. #include "omatch.p"
  2120.   begin
  2121.     while ((PAT[J] <> ENDSTR) and
  2122.            (I > 0)) do
  2123.       if (OMATCH(LIN, I, PAT, J)) then
  2124.         J := J+PATSIZE(PAT, J)
  2125.       else
  2126.         I := 0;                 { no match possible }
  2127.     AMATCH := I
  2128.   end;
  2129.  
  2130. {!amatch.p!}
  2131.   { amatch -- look for match of pat[j]... at lin[offset]... }
  2132.   function AMATCH(var LIN : STRINGZ;
  2133.                   OFFSET : Integer;
  2134.                   var PAT : STRINGZ;
  2135.                   J : Integer) : Integer;
  2136.   var
  2137.     I, K : Integer;
  2138.     DONE : Boolean;
  2139.  
  2140. #include "omatch.p"
  2141. #include "patsize.p"
  2142.   begin
  2143.     DONE := False;
  2144.     while ((not DONE) and
  2145.            (PAT[J] <> ENDSTR)) do
  2146.       if (PAT[J] = CLOSURE) then
  2147.         begin
  2148.           J := J+PATSIZE(PAT, J); { step over CLOSURE }
  2149.           I := OFFSET;
  2150.           { match as many as possible }
  2151.           while ((not DONE) and
  2152.                  (LIN[I] <> ENDSTR)) do
  2153.             if (not OMATCH(LIN, I, PAT, J)) then
  2154.               DONE := True;
  2155.           { i points to input character that made us fail }
  2156.           { match rest of pattern against rest of input }
  2157.           { shrink closure by 1 after each failure }
  2158.           DONE := False;
  2159.           while ((not DONE) and
  2160.                  (I >= OFFSET)) do
  2161.             begin
  2162.               K := AMATCH(LIN, I, PAT, J+PATSIZE(PAT, J));
  2163.               if (K > 0) then   { matched rest of pattern }
  2164.                 DONE := True
  2165.               else
  2166.                 I := I-1
  2167.             end;
  2168.           OFFSET := K;          { if k = 0 failure else success }
  2169.           DONE := True
  2170.         end
  2171.       else if (not OMATCH(LIN, OFFSET, PAT, J)) then
  2172.         begin
  2173.           OFFSET := 0;          { non-closure }
  2174.           DONE := True
  2175.         end
  2176.       else                      { omatch succeeded on this pattern element }
  2177.         J := J+PATSIZE(PAT, J);
  2178.     AMATCH := OFFSET
  2179.   end;
  2180.  
  2181. {!patsize.p!}
  2182.   { patsize -- returns size of pattern entry at pat[n] }
  2183.   function PATSIZE(var PAT : STRINGZ;
  2184.                    N : Integer) : Integer;
  2185.   begin
  2186.     if (not(PAT[N] in [LITCHAR, BOL, EOL, ANY, CCL, NCCL, CLOSURE])) then
  2187.       ERROR('in patsize: can''t happen')
  2188.     else
  2189.       case PAT[N] of
  2190.         LITCHAR :
  2191.           PATSIZE := 2;
  2192.         BOL, EOL, ANY :
  2193.           PATSIZE := 1;
  2194.         CCL, NCCL :
  2195.           PATSIZE := PAT[N+1]+2;
  2196.         CLOSURE :
  2197.           PATSIZE := CLOSIZE
  2198.       end
  2199.   end;
  2200.  
  2201. {!omatch.p!}
  2202.   { omatch -- match one pattern element at pat[j] }
  2203.   function OMATCH(var LIN : STRINGZ;
  2204.                   var I : Integer;
  2205.                   var PAT : STRINGZ;
  2206.                   J : Integer) : Boolean;
  2207.   var
  2208.     ADVANCE : -1..1;
  2209.  
  2210. #include "locate.p"
  2211.   begin
  2212.     ADVANCE := -1;
  2213.     if (LIN[I] = ENDSTR) then
  2214.       OMATCH := False
  2215.     else if (not(PAT[J] in [LITCHAR, BOL, EOL, ANY, CCL, NCCL, CLOSURE])) then
  2216.       ERROR('in omatch: can''t happen')
  2217.     else
  2218.       case PAT[J] of
  2219.         LITCHAR :
  2220.           if (LIN[I] = PAT[J+1]) then
  2221.             ADVANCE := 1;
  2222.         BOL :
  2223.           if (I = 1) then
  2224.             ADVANCE := 0;
  2225.         ANY :
  2226.           if (LIN[I] <> NEWLINE) then
  2227.             ADVANCE := 1;
  2228.         EOL :
  2229.           if (LIN[I] = NEWLINE) then
  2230.             ADVANCE := 0;
  2231.         CCL :
  2232.           if (LOCATE(LIN[I], PAT, J+1)) then
  2233.             ADVANCE := 1;
  2234.         NCCL :
  2235.           if ((LIN[I] <> NEWLINE) and
  2236.               (not LOCATE(LIN[I], PAT, J+1))) then
  2237.             ADVANCE := 1
  2238.       end;
  2239.     if (ADVANCE >= 0) then
  2240.       begin
  2241.         I := I+ADVANCE;
  2242.         OMATCH := True
  2243.       end
  2244.     else
  2245.       OMATCH := False
  2246.   end;
  2247.  
  2248. {!locate.p!}
  2249.   { locate -- look for c in character class at pat[offset] }
  2250.   function LOCATE(C : CHARACTER;
  2251.                   var PAT : STRINGZ;
  2252.                   OFFSET : Integer) : Boolean;
  2253.   var
  2254.     I : Integer;
  2255.   begin
  2256.     { size of class is at pat[offset], characters follow }
  2257.     LOCATE := False;
  2258.     I := OFFSET+PAT[OFFSET];    { last position }
  2259.     while (I > OFFSET) do
  2260.       if (C = PAT[I]) then
  2261.         begin
  2262.           LOCATE := True;
  2263.           I := OFFSET           { force loop termination }
  2264.         end
  2265.       else
  2266.         I := I-1
  2267.   end;
  2268.  
  2269. {!patsize1.p!}
  2270.   { patsize -- returns size of pattern entry at pat[n] }
  2271.   function PATSIZE(var PAT : STRINGZ;
  2272.                    N : Integer) : Integer;
  2273.   begin
  2274.     if (PAT[N] = LITCHAR) then
  2275.       PATSIZE := 2
  2276.     else if (PAT[N] in [BOL, EOL, ANY]) then
  2277.       PATSIZE := 1
  2278.     else if ((PAT[N] = CCL) or
  2279.              (PAT[N] = NCCL)) then
  2280.       PATSIZE := PAT[N+1]+2
  2281.     else if (PAT[N] = CLOSURE) then
  2282.       PATSIZE := CLOSIZE
  2283.     else
  2284.       ERROR('in patsize: can''t happen')
  2285.   end;
  2286.  
  2287. {!getpat.p!}
  2288.   { getpat -- convert arguments into pattern }
  2289.   function GETPAT(var ARG, PAT : STRINGZ) : Boolean;
  2290.  
  2291. #include "makepat.p"
  2292.   begin
  2293.     GETPAT := (MAKEPAT(ARG, 1, ENDSTR, PAT) > 0)
  2294.   end;
  2295.  
  2296. {!makepat.p!}
  2297.   { makepat -- make pattern from arg[i], terminate at delims }
  2298.   function MAKEPAT(var ARG : STRINGZ;
  2299.                    START : Integer;
  2300.                    DELIM : CHARACTER;
  2301.                    var PAT : STRINGZ) : Integer;
  2302.   var
  2303.     I, J, LASTJ, LJ : Integer;
  2304.     DONE, JUNK : Boolean;
  2305.  
  2306. #include "getccl.p"
  2307. #include "stclose.p"
  2308.   begin
  2309.     J := 1;                     { pat index }
  2310.     I := START;                 { arg index }
  2311.     LASTJ := 1;
  2312.     DONE := False;
  2313.     while ((not DONE) and
  2314.            (ARG[I] <> DELIM) and
  2315.            (ARG[I] <> ENDSTR)) do
  2316.       begin
  2317.         LJ := J;
  2318.         if (ARG[I] = ANY) then
  2319.           JUNK := ADDSTR(ANY, PAT, J, MAXPAT)
  2320.         else if ((ARG[I] = BOL) and
  2321.                  (I = START)) then
  2322.           JUNK := ADDSTR(BOL, PAT, J, MAXPAT)
  2323.         else if ((ARG[I] = EOL) and
  2324.                  (ARG[I+1] = DELIM)) then
  2325.           JUNK := ADDSTR(EOL, PAT, J, MAXPAT)
  2326.         else if (ARG[I] = CCL) then
  2327.           DONE := (GETCCL(ARG, I, PAT, J) = False)
  2328.         else if ((ARG[I] = CLOSURE) and
  2329.                  (I > START)) then
  2330.           begin
  2331.             LJ := LASTJ;
  2332.             if (PAT[LJ] in [BOL, EOL, CLOSURE]) then
  2333.               DONE := True      { force loop termination }
  2334.             else
  2335.               STCLOSE(PAT, J, LASTJ)
  2336.           end
  2337.         else
  2338.           begin
  2339.             JUNK := ADDSTR(LITCHAR, PAT, J, MAXPAT);
  2340.             JUNK := ADDSTR(ESC(ARG, I), PAT, J, MAXPAT)
  2341.           end;
  2342.         LASTJ := LJ;
  2343.         if (not DONE) then
  2344.           I := I+1
  2345.       end;
  2346.     if ((DONE) or
  2347.         (ARG[I] <> DELIM)) then { finished early }
  2348.       MAKEPAT := 0
  2349.     else if (not ADDSTR(ENDSTR, PAT, J, MAXPAT)) then
  2350.       MAKEPAT := 0              { no room }
  2351.     else
  2352.       MAKEPAT := I              { all is well }
  2353.   end;
  2354.  
  2355. {!getccl.p!}
  2356.   { getccl -- expand char class at arg[i] into pat[j] }
  2357.   function GETCCL(var ARG : STRINGZ;
  2358.                   var I : Integer;
  2359.                   var PAT : STRINGZ;
  2360.                   var J : Integer) : Boolean;
  2361.   var
  2362.     JSTART : Integer;
  2363.     JUNK : Boolean;
  2364.  
  2365. #include "dodash.p"
  2366.   begin
  2367.     I := I+1;                   { slip over '[' }
  2368.     if (ARG[I] = NEGATE) then
  2369.       begin
  2370.         JUNK := ADDSTR(NCCL, PAT, J, MAXPAT);
  2371.         I := I+1
  2372.       end
  2373.     else
  2374.       JUNK := ADDSTR(CCL, PAT, J, MAXPAT);
  2375.     JSTART := J;
  2376.     JUNK := ADDSTR(0, PAT, J, MAXPAT); { room for count }
  2377.     DODASH(CCLEND, ARG, I, PAT, J, MAXPAT);
  2378.     PAT[JSTART] := J-JSTART-1;
  2379.     GETCCL := (ARG[I] = CCLEND)
  2380.   end;
  2381.  
  2382. {!stclose.p!}
  2383.   { stclose -- insert closure entry at pat[j] }
  2384.   procedure STCLOSE(var PAT : STRINGZ;
  2385.                     var J : Integer;
  2386.                     LASTJ : Integer);
  2387.   var
  2388.     JP, JT : Integer;
  2389.     JUNK : Boolean;
  2390.   begin
  2391.     for JP := J-1 downto LASTJ do
  2392.       begin
  2393.         JT := JP+CLOSIZE;
  2394.         JUNK := ADDSTR(PAT[JP], PAT, JT, MAXPAT)
  2395.       end;
  2396.     J := J+CLOSIZE;
  2397.     PAT[LASTJ] := CLOSURE       { where original pattern began }
  2398.   end;
  2399.  
  2400. {!findcons.p!}
  2401.   { findcons -- const declarations for find }
  2402. const
  2403.   MAXPAT = MAXSTR;
  2404.   CLOSIZE = 1;                  { size of a closure entry }
  2405.   CLOSURE = STAR;
  2406.   BOL = PERCENT;
  2407.   EOL = DOLLAR;
  2408.   ANY = QUESTION;
  2409.   CCL = LBRACK;
  2410.   CCLEND = RBRACK;
  2411.   NEGATE = CARET;
  2412.   NCCL = EXCLAM;                { cannot be the same as NEGATE }
  2413.   LITCHAR = Ord('c');
  2414.  
  2415. {!change.p!}
  2416.   { change -- change "from" into "to" on each line }
  2417.   procedure CHANGE;
  2418.  
  2419. #include "findcons.p"
  2420.     DITTO = 255;                {TP7}
  2421.   var
  2422.     LIN, PAT, SUB, ARG : STRINGZ;
  2423.  
  2424. #include "getpat.p"
  2425. #include "getsub.p"
  2426. #include "subline.p"
  2427.   begin
  2428.     if (not GETARG(1, ARG, MAXSTR)) then
  2429.       ERROR('usage: change from [to]');
  2430.     if (not GETPAT(ARG, PAT)) then
  2431.       ERROR('change: illegal "from" pattern');
  2432.     if (not GETARG(2, ARG, MAXSTR)) then
  2433.       ARG[1] := ENDSTR;
  2434.     if (not GETSUB(ARG, SUB)) then
  2435.       ERROR('change: illegal "to" string');
  2436.     while (GETLINE(LIN, STDIN, MAXSTR)) do
  2437.       SUBLINE(LIN, PAT, SUB)
  2438.   end;
  2439.  
  2440. {!subline.p!}
  2441.   { subline -- substitute sub for pat in lin and print }
  2442.   procedure SUBLINE(var LIN, PAT, SUB : STRINGZ);
  2443.   var
  2444.     I, LASTM, M : Integer;
  2445.     JUNK : Boolean;
  2446.  
  2447. #include "amatch.p"
  2448. #include "putsub.p"
  2449.   begin
  2450.     LASTM := 0;
  2451.     I := 1;
  2452.     while (LIN[I] <> ENDSTR) do
  2453.       begin
  2454.         M := AMATCH(LIN, I, PAT, 1);
  2455.         if ((M > 0) and
  2456.             (LASTM <> M)) then
  2457.           begin
  2458.             { replace matched text }
  2459.             PUTSUB(LIN, I, M, SUB);
  2460.             LASTM := M
  2461.           end;
  2462.         if ((M = 0) or
  2463.             (M = I)) then
  2464.           begin
  2465.             { no match or null match }
  2466.             PUTC(LIN[I]);
  2467.             I := I+1
  2468.           end
  2469.         else                    { skip matched text }
  2470.           I := M
  2471.       end
  2472.   end;
  2473.  
  2474. {!getsub.p!}
  2475.   { getsub -- get substitution string into sub }
  2476.   function GETSUB(var ARG, SUB : STRINGZ) : Boolean;
  2477.  
  2478. #include "makesub.p"
  2479.   begin
  2480.     GETSUB := (MAKESUB(ARG, 1, ENDSTR, SUB) > 0)
  2481.   end;
  2482.  
  2483. {!makesub.p!}
  2484.   { makesub -- make substitution string from arg in sub }
  2485.   function MAKESUB(var ARG : STRINGZ;
  2486.                    FROM : Integer;
  2487.                    DELIM : CHARACTER;
  2488.                    var SUB : STRINGZ) : Integer;
  2489.   var
  2490.     I, J : Integer;
  2491.     JUNK : Boolean;
  2492.   begin
  2493.     J := 1;
  2494.     I := FROM;
  2495.     while ((ARG[I] <> DELIM) and
  2496.            (ARG[I] <> ENDSTR)) do
  2497.       begin
  2498.         if (ARG[I] = Ord('&')) then
  2499.           JUNK := ADDSTR(DITTO, SUB, J, MAXPAT)
  2500.         else
  2501.           JUNK := ADDSTR(ESC(ARG, I), SUB, J, MAXPAT);
  2502.         I := I+1
  2503.       end;
  2504.     if (ARG[I] <> DELIM) then   { missing delimiter }
  2505.       MAKESUB := 0
  2506.     else if (not ADDSTR(ENDSTR, SUB, J, MAXPAT)) then
  2507.       MAKESUB := 0
  2508.     else
  2509.       MAKESUB := I
  2510.   end;
  2511.  
  2512. {!putsub.p!}
  2513.   { putsub -- output substitution text }
  2514.   procedure PUTSUB(var LIN : STRINGZ;
  2515.                    S1, S2 : Integer;
  2516.                    var SUB : STRINGZ);
  2517.   var
  2518.     I, J : Integer;
  2519.     JUNK : Boolean;
  2520.   begin
  2521.     I := 1;
  2522.     while (SUB[I] <> ENDSTR) do
  2523.       begin
  2524.         if (SUB[I] = DITTO) then
  2525.           for J := S1 to S2-1 do
  2526.             PUTC(LIN[J])
  2527.         else
  2528.           PUTC(SUB[I]);
  2529.         I := I+1
  2530.       end
  2531.   end;
  2532.  
  2533. {!getlist.p!}
  2534.   { getlist -- get list of line nums at lin[i], increment i }
  2535.   function GETLIST(var LIN : STRINGZ;
  2536.                    var I : Integer;
  2537.                    var STATUS : STCODE) : STCODE;
  2538.   var
  2539.     NUM : Integer;
  2540.     DONE : Boolean;
  2541.   begin
  2542.     LINE2 := 0;
  2543.     NLINES := 0;
  2544.     DONE := (GETONE(LIN, I, NUM, STATUS) <> OK);
  2545.     while (not DONE) do
  2546.       begin
  2547.         LINE1 := LINE2;
  2548.         LINE2 := NUM;
  2549.         NLINES := NLINES+1;
  2550.         if (LIN[I] = SEMICOL) then
  2551.           CURLN := NUM;
  2552.         if ((LIN[I] = COMMA) or
  2553.             (LIN[I] = SEMICOL)) then
  2554.           begin
  2555.             I := I+1;
  2556.             DONE := (GETONE(LIN, I, NUM, STATUS) <> OK)
  2557.           end
  2558.         else
  2559.           DONE := True
  2560.       end;
  2561.     NLINES := MIN(NLINES, 2);
  2562.     if (NLINES = 0) then
  2563.       LINE2 := CURLN;
  2564.     if (NLINES <= 1) then
  2565.       LINE1 := LINE2;
  2566.     if (STATUS <> ERR) then
  2567.       STATUS := OK;
  2568.     GETLIST := STATUS
  2569.   end;
  2570.  
  2571. {!getone.p!}
  2572.   { getone -- get one line number expression }
  2573.   function GETONE(var LIN : STRINGZ;
  2574.                   var I, NUM : Integer;
  2575.                   var STATUS : STCODE) : STCODE;
  2576.   var
  2577.     ISTART, MUL, PNUM : Integer;
  2578.   begin
  2579.     ISTART := I;
  2580.     NUM := 0;
  2581.     if (GETNUM(LIN, I, NUM, STATUS) = OK) then { 1st term }
  2582.       repeat                    { + or - terms }
  2583.         SKIPBL(LIN, I);
  2584.         if ((LIN[I] <> PLUS) and
  2585.             (LIN[I] <> MINUS)) then
  2586.           STATUS := ENDDATA
  2587.         else
  2588.           begin
  2589.             if (LIN[I] = PLUS) then
  2590.               MUL := +1
  2591.             else
  2592.               MUL := -1;
  2593.             I := I+1;
  2594.             if (GETNUM(LIN, I, PNUM, STATUS) = OK) then
  2595.               NUM := NUM+MUL*PNUM;
  2596.             if (STATUS = ENDDATA) then
  2597.               STATUS := ERR
  2598.           end
  2599.       until (STATUS <> OK);
  2600.     if ((NUM < 0) or
  2601.         (NUM > LASTLN)) then
  2602.       STATUS := ERR;
  2603.     if (STATUS <> ERR) then
  2604.       begin
  2605.         if (I <= ISTART) then
  2606.           STATUS := ENDDATA
  2607.         else
  2608.           STATUS := OK
  2609.       end;
  2610.     GETONE := STATUS
  2611.   end;
  2612.  
  2613. {!skipbl.p!}
  2614.   { skipbl -- skip blanks and tabs at s[i]... }
  2615.   procedure SKIPBL(var S : STRINGZ;
  2616.                    var I : Integer);
  2617.   begin
  2618.     while ((S[I] = BLANK) or
  2619.            (S[I] = TAB)) do
  2620.       I := I+1
  2621.   end;
  2622.  
  2623. {!getnum.p!}
  2624.   { getnum -- get single line number component }
  2625.   function GETNUM(var LIN : STRINGZ;
  2626.                   var I, NUM : Integer;
  2627.                   var STATUS : STCODE) : STCODE;
  2628.   begin
  2629.     STATUS := OK;
  2630.     SKIPBL(LIN, I);
  2631.     if (ISDIGIT(LIN[I])) then
  2632.       begin
  2633.         NUM := CTOI(LIN, I);
  2634.         I := I-1                { move back; to be advanced at end }
  2635.       end
  2636.     else if (LIN[I] = CURLINE) then
  2637.       NUM := CURLN
  2638.     else if (LIN[I] = LASTLINE) then
  2639.       NUM := LASTLN
  2640.     else if ((LIN[I] = SCAN) or
  2641.              (LIN[I] = BACKSCAN)) then
  2642.       begin
  2643.         if (OPTPAT(LIN, I) = ERR) then { build pattern }
  2644.           STATUS := ERR
  2645.         else
  2646.           STATUS := PATSCAN(LIN[I], NUM)
  2647.       end
  2648.     else
  2649.       STATUS := ENDDATA;
  2650.     if (STATUS = OK) then
  2651.       I := I+1;                 { next character to be examined }
  2652.     GETNUM := STATUS
  2653.   end;
  2654.  
  2655. {!optpat.p!}
  2656.   { optpat -- get optional pattern from lin[i], increment i }
  2657.   function OPTPAT(var LIN : STRINGZ;
  2658.                   var I : Integer) : STCODE;
  2659.  
  2660. #include "makepat.p"
  2661.   begin
  2662.     if (LIN[I] = ENDSTR) then
  2663.       I := 0
  2664.     else if (LIN[I+1] = ENDSTR) then
  2665.       I := 0
  2666.     else if (LIN[I+1] = LIN[I]) then { repeated delimiter }
  2667.       I := I+1                  { leave existing pattern alone }
  2668.     else
  2669.       I := MAKEPAT(LIN, I+1, LIN[I], PAT);
  2670.     if (PAT[1] = ENDSTR) then
  2671.       I := 0;
  2672.     if (I = 0) then
  2673.       begin
  2674.         PAT[1] := ENDSTR;
  2675.         OPTPAT := ERR
  2676.       end
  2677.     else
  2678.       OPTPAT := OK
  2679.   end;
  2680.  
  2681. {!patscan.p!}
  2682.   { patscan -- find next occurrence of pattern after line n }
  2683.   function PATSCAN(WAY : CHARACTER;
  2684.                    var N : Integer) : STCODE;
  2685.   var
  2686.     DONE : Boolean;
  2687.     LINE : STRINGZ;
  2688.   begin
  2689.     N := CURLN;
  2690.     PATSCAN := ERR;
  2691.     DONE := False;
  2692.     repeat
  2693.       if (WAY = SCAN) then
  2694.         N := NEXTLN(N)
  2695.       else
  2696.         N := PREVLN(N);
  2697.       GETTXT(N, LINE);
  2698.       if (MATCH(LINE, PAT)) then
  2699.         begin
  2700.           PATSCAN := OK;
  2701.           DONE := True
  2702.         end
  2703.     until ((N = CURLN) or
  2704.            (DONE))
  2705.   end;
  2706.  
  2707. {!nextln.p!}
  2708.   { nextln -- get line after n }
  2709.   function NEXTLN(N : Integer) : Integer;
  2710.   begin
  2711.     if (N >= LASTLN) then
  2712.       NEXTLN := 0
  2713.     else
  2714.       NEXTLN := N+1
  2715.   end;
  2716.  
  2717. {!prevln.p!}
  2718.   { prevln -- get line before n }
  2719.   function PREVLN(N : Integer) : Integer;
  2720.   begin
  2721.     if (N <= 0) then
  2722.       PREVLN := LASTLN
  2723.     else
  2724.       PREVLN := N-1
  2725.   end;
  2726.  
  2727. {!default.p!}
  2728.   { default -- set defaulted line numbers }
  2729.   function DEFAULT(DEF1, DEF2 : Integer;
  2730.                    var STATUS : STCODE) : STCODE;
  2731.   begin
  2732.     if (NLINES = 0) then
  2733.       begin
  2734.         LINE1 := DEF1;
  2735.         LINE2 := DEF2
  2736.       end;
  2737.     if ((LINE1 > LINE2) or
  2738.         (LINE1 <= 0)) then
  2739.       STATUS := ERR
  2740.     else
  2741.       STATUS := OK;
  2742.     DEFAULT := STATUS
  2743.   end;
  2744.  
  2745. {!doprint.p!}
  2746.   { doprint -- print lines n1 through n2 }
  2747.   function DOPRINT(N1, N2 : Integer) : STCODE;
  2748.   var
  2749.     I : Integer;
  2750.     LINE : STRINGZ;
  2751.   begin
  2752.     if (N1 <= 0) then
  2753.       DOPRINT := ERR
  2754.     else
  2755.       begin
  2756.         for I := N1 to N2 do
  2757.           begin
  2758.             GETTXT(I, LINE);
  2759.             PUTSTR(LINE, STDOUT)
  2760.           end;
  2761.         CURLN := N2;
  2762.         DOPRINT := OK
  2763.       end
  2764.   end;
  2765.  
  2766. {!appendz.p!}
  2767.   { appendz -- append lines after "line" }
  2768.   function APPENDZ(LINE : Integer;
  2769.                    GLOB : Boolean) : STCODE;
  2770.   var
  2771.     INLINEZ : STRINGZ;
  2772.     STAT : STCODE;
  2773.     DONE : Boolean;
  2774.   begin
  2775.     if (GLOB) then
  2776.       STAT := ERR
  2777.     else
  2778.       begin
  2779.         CURLN := LINE;
  2780.         STAT := OK;
  2781.         DONE := False;
  2782.         while ((not DONE) and
  2783.                (STAT = OK)) do
  2784.           if (not GETLINE(INLINEZ, STDIN, MAXSTR)) then
  2785.             STAT := ENDDATA
  2786.           else if (INLINEZ[1] = PERIOD)
  2787.           and (INLINEZ[2] = NEWLINE) then
  2788.             DONE := True
  2789.           else if (PUTTXT(INLINEZ) = ERR) then
  2790.             STAT := ERR
  2791.       end;
  2792.     APPENDZ := STAT
  2793.   end;
  2794.  
  2795. {!clrbuf1.p!}
  2796.   { clrbuf -- (in memory) initialize for new file }
  2797.   procedure CLRBUF;
  2798.   begin
  2799.     { nothing to do }
  2800.   end;
  2801.  
  2802. {!gettxt1.p!}
  2803.   { gettxt -- (in memory) get text from line n into s }
  2804.   procedure GETTXT(N : Integer;
  2805.                    var S : STRINGZ);
  2806.   begin
  2807.     SCOPY(BUF[N].TXT, 1, S, 1)
  2808.   end;
  2809.  
  2810. {!blkmove.p!}
  2811.   { blkmove -- move block of lines n1..n2 to after n3 }
  2812.   procedure BLKMOVE(N1, N2, N3 : Integer);
  2813.   begin
  2814.     if (N3 < N1-1) then
  2815.       begin
  2816.         REVERSE(N3+1, N1-1);
  2817.         REVERSE(N1, N2);
  2818.         REVERSE(N3+1, N2)
  2819.       end
  2820.     else if (N3 > N2) then
  2821.       begin
  2822.         REVERSE(N1, N2);
  2823.         REVERSE(N2+1, N3);
  2824.         REVERSE(N1, N3)
  2825.       end
  2826.   end;
  2827.  
  2828. {!reverse.p!}
  2829.   { reverse -- reverse buf[n1]...buf[n2] }
  2830.   procedure REVERSE(N1, N2 : Integer);
  2831.   var
  2832.     TEMP : BUFTYPE;
  2833.   begin
  2834.     while (N1 < N2) do
  2835.       begin
  2836.         TEMP := BUF[N1];
  2837.         BUF[N1] := BUF[N2];
  2838.         BUF[N2] := TEMP;
  2839.         N1 := N1+1;
  2840.         N2 := N2-1
  2841.       end
  2842.   end;
  2843.  
  2844. {!setbuf1.p!}
  2845.   { setbuf -- (in memory) initialize line storage buffer }
  2846.   procedure SETBUF;
  2847.   var
  2848.     NULL : STRINGZ;             { value is '' }
  2849.   begin
  2850.     NULL[1] := ENDSTR;
  2851.     SCOPY(NULL, 1, BUF[0].TXT, 1);
  2852.     CURLN := 0;
  2853.     LASTLN := 0;
  2854.   end;
  2855.  
  2856. {!puttxt1.p!}
  2857.   { puttxt -- (in memory) put text from lin after curln }
  2858.   function PUTTXT(var LIN : STRINGZ) : STCODE;
  2859.   begin
  2860.     PUTTXT := ERR;
  2861.     if (LASTLN < MAXLINES) then
  2862.       begin
  2863.         LASTLN := LASTLN+1;
  2864.         SCOPY(LIN, 1, BUF[LASTLN].TXT, 1);
  2865.         PUTMARK(LASTLN, False);
  2866.         BLKMOVE(LASTLN, LASTLN, CURLN);
  2867.         CURLN := CURLN+1;
  2868.         PUTTXT := OK
  2869.       end
  2870.   end;
  2871.  
  2872. {!ckp.p!}
  2873.   { ckp -- check for "p" after command }
  2874.   function CKP(var LIN : STRINGZ;
  2875.                I : Integer;
  2876.                var PFLAG : Boolean;
  2877.                var STATUS : STCODE) : STCODE;
  2878.   begin
  2879.     SKIPBL(LIN, I);
  2880.     if (LIN[I] = PCMD) then
  2881.       begin
  2882.         I := I+1;
  2883.         PFLAG := True
  2884.       end
  2885.     else
  2886.       PFLAG := False;
  2887.     if (LIN[I] = NEWLINE) then
  2888.       STATUS := OK
  2889.     else
  2890.       STATUS := ERR;
  2891.     CKP := STATUS
  2892.   end;
  2893.  
  2894. {!lndelete.p!}
  2895.   { lndelete -- delete lines n1 through n2 }
  2896.   function LNDELETE(N1, N2 : Integer;
  2897.                     var STATUS : STCODE) : STCODE;
  2898.   begin
  2899.     if (N1 <= 0) then
  2900.       STATUS := ERR
  2901.     else
  2902.       begin
  2903.         BLKMOVE(N1, N2, LASTLN);
  2904.         LASTLN := LASTLN-(N2-N1+1);
  2905.         CURLN := PREVLN(N1);
  2906.         STATUS := OK
  2907.       end;
  2908.     LNDELETE := STATUS
  2909.   end;
  2910.  
  2911. {!movez.p!}
  2912.   { movez -- move line1 through line2 after line3 }
  2913.   function MOVEZ(LINE3 : Integer) : STCODE;
  2914.   begin
  2915.     if ((LINE1 <= 0) or
  2916.         ((LINE3 >= LINE1) and
  2917.          (LINE3 < LINE2))) then
  2918.       MOVEZ := ERR
  2919.     else
  2920.       begin
  2921.         BLKMOVE(LINE1, LINE2, LINE3);
  2922.         if (LINE3 > LINE1) then
  2923.           CURLN := LINE3
  2924.         else
  2925.           CURLN := LINE3+(LINE2-LINE1+1);
  2926.         MOVEZ := OK
  2927.       end
  2928.   end;
  2929.  
  2930. {!getrhs.p!}
  2931.   { getrhs -- get right hand side of "s" command }
  2932.   function GETRHS(var LIN : STRINGZ;
  2933.                   var I : Integer;
  2934.                   var SUB : STRINGZ;
  2935.                   var GFLAG : Boolean) : STCODE;
  2936.   begin
  2937.     GETRHS := OK;
  2938.     if (LIN[I] = ENDSTR) then
  2939.       GETRHS := ERR
  2940.     else if (LIN[I+1] = ENDSTR) then
  2941.       GETRHS := ERR
  2942.     else
  2943.       begin
  2944.         I := MAKESUB(LIN, I+1, LIN[I], SUB);
  2945.         if (I = 0) then
  2946.           GETRHS := ERR
  2947.         else if (LIN[I+1] = Ord('g')) then
  2948.           begin
  2949.             I := I+1;
  2950.             GFLAG := True
  2951.           end
  2952.         else
  2953.           GFLAG := False
  2954.       end
  2955.   end;
  2956.  
  2957. {!subst.p!}
  2958.   { subst -- substitute "sub" for occurrences of pattern }
  2959.   function SUBST(var SUB : STRINGZ;
  2960.                  GFLAG, GLOB : Boolean) : STCODE;
  2961.   var
  2962.     NEWZ, OLD : STRINGZ;
  2963.     J, K, LASTM, LINE, M : Integer;
  2964.     STAT : STCODE;
  2965.     DONE, SUBBED, JUNK : Boolean;
  2966.   begin
  2967.     if (GLOB) then
  2968.       STAT := OK
  2969.     else
  2970.       STAT := ERR;
  2971.     DONE := (LINE1 <= 0);
  2972.     LINE := LINE1;
  2973.     while ((not DONE) and
  2974.            (LINE <= LINE2)) do
  2975.       begin
  2976.         J := 1;
  2977.         SUBBED := False;
  2978.         GETTXT(LINE, OLD);
  2979.         LASTM := 0;
  2980.         K := 1;
  2981.         while (OLD[K] <> ENDSTR) do
  2982.           begin
  2983.             if ((GFLAG) or
  2984.                 (not SUBBED)) then
  2985.               M := AMATCH(OLD, K, PAT, 1)
  2986.             else
  2987.               M := 0;
  2988.             if ((M > 0) and
  2989.                 (LASTM <> M)) then
  2990.               begin
  2991.                 { replace matched text }
  2992.                 SUBBED := True;
  2993.                 CATSUB(OLD, K, M, SUB, NEWZ, J, MAXSTR);
  2994.                 LASTM := M
  2995.               end;
  2996.             if ((M = 0) or
  2997.                 (M = K)) then
  2998.               begin
  2999.                 { no match or null match }
  3000.                 JUNK := ADDSTR(OLD[K], NEWZ, J, MAXSTR);
  3001.                 K := K+1
  3002.               end
  3003.             else                { skipped matched text }
  3004.               K := M
  3005.           end;
  3006.         if (SUBBED) then
  3007.           begin
  3008.             if (not ADDSTR(ENDSTR, NEWZ, J, MAXSTR)) then
  3009.               begin
  3010.                 STAT := ERR;
  3011.                 DONE := True
  3012.               end
  3013.             else
  3014.               begin
  3015.                 STAT := LNDELETE(LINE, LINE, STATUS);
  3016.                 STAT := PUTTXT(NEWZ);
  3017.                 LINE2 := LINE2+CURLN-LINE;
  3018.                 LINE := CURLN;
  3019.                 if (STAT = ERR) then
  3020.                   DONE := True
  3021.                 else
  3022.                   STAT := OK
  3023.               end
  3024.           end;
  3025.         LINE := LINE+1
  3026.       end;
  3027.     SUBST := STAT
  3028.   end;
  3029.  
  3030. {!catsub.p!}
  3031.   { catsub -- add replacement text to end of new }
  3032.   procedure CATSUB(var LIN : STRINGZ;
  3033.                    S1, S2 : Integer;
  3034.                    var SUB : STRINGZ;
  3035.                    var NEWZ : STRINGZ;
  3036.                    var K : Integer;
  3037.                    MAXNEW : Integer);
  3038.   var
  3039.     I, J : Integer;
  3040.     JUNK : Boolean;
  3041.   begin
  3042.     I := 1;
  3043.     while (SUB[I] <> ENDSTR) do
  3044.       begin
  3045.         if (SUB[I] = DITTO) then
  3046.           for J := S1 to S2-1 do
  3047.             JUNK := ADDSTR(LIN[J], NEWZ, K, MAXNEW)
  3048.         else
  3049.           JUNK := ADDSTR(SUB[I], NEWZ, K, MAXNEW);
  3050.         I := I+1
  3051.       end
  3052.   end;
  3053.  
  3054. {!getfn.p!}
  3055.   { getfn -- get file name from lin[i]... }
  3056.   function GETFN(var LIN : STRINGZ;
  3057.                  var I : Integer;
  3058.                  var FIL : STRINGZ) : STCODE;
  3059.   var
  3060.     K : Integer;
  3061.     STAT : STCODE;
  3062.  
  3063. #include "getword.p"
  3064.   begin
  3065.     STAT := ERR;
  3066.     if (LIN[I+1] = BLANK) then
  3067.       begin
  3068.         K := GETWORD(LIN, I+2, FIL); { get new filename }
  3069.         if (K > 0) then
  3070.           if (LIN[K] = NEWLINE) then
  3071.             STAT := OK
  3072.       end
  3073.     else if ((LIN[I+1] = NEWLINE) and
  3074.              (SAVEFILE[1] <> ENDSTR)) then
  3075.       begin
  3076.         SCOPY(SAVEFILE, 1, FIL, 1);
  3077.         STAT := OK;
  3078.       end;
  3079.     if ((STAT = OK) and
  3080.         (SAVEFILE[1] = ENDSTR)) then
  3081.       SCOPY(FIL, 1, SAVEFILE, 1); { save if no old one }
  3082.     GETFN := STAT
  3083.   end;
  3084.  
  3085. {!doread.p!}
  3086.   { doread -- read "fil" after line n }
  3087.   function DOREAD(N : Integer;
  3088.                   var FIL : STRINGZ) : STCODE;
  3089.   var
  3090.     COUNT : Integer;
  3091.     T : Boolean;
  3092.     STAT : STCODE;
  3093.     FD : FILEDESC;
  3094.     INLINEZ : STRINGZ;
  3095.   begin
  3096.     FD := OPEN(FIL, IOREAD);
  3097.     if (FD = IOERROR) then
  3098.       STAT := ERR
  3099.     else
  3100.       begin
  3101.         CURLN := N;
  3102.         STAT := OK;
  3103.         COUNT := 0;
  3104.         repeat
  3105.           T := GETLINE(INLINEZ, FD, MAXSTR);
  3106.           if (T) then
  3107.             begin
  3108.               STAT := PUTTXT(INLINEZ);
  3109.               if (STAT <> ERR) then
  3110.                 COUNT := COUNT+1
  3111.             end
  3112.         until ((STAT <> OK) or
  3113.                (T = False));
  3114.         CLOSEZ(FD);
  3115.         PUTDEC(COUNT, 1);
  3116.         PUTC(NEWLINE)
  3117.       end;
  3118.     DOREAD := STAT
  3119.   end;
  3120.  
  3121. {!dowrite.p!}
  3122.   { dowrite -- write lines n1..n2 into file }
  3123.   function DOWRITE(N1, N2 : Integer;
  3124.                    var FIL : STRINGZ) : STCODE;
  3125.   var
  3126.     I : Integer;
  3127.     FD : FILEDESC;
  3128.     LINE : STRINGZ;
  3129.   begin
  3130.     FD := CREATE(FIL, IOWRITE);
  3131.     if (FD = IOERROR) then
  3132.       DOWRITE := ERR
  3133.     else
  3134.       begin
  3135.         for I := N1 to N2 do
  3136.           begin
  3137.             GETTXT(I, LINE);
  3138.             PUTSTR(LINE, FD)
  3139.           end;
  3140.         CLOSEZ(FD);
  3141.         PUTDEC(N2-N1+1, 1);
  3142.         PUTC(NEWLINE);
  3143.         DOWRITE := OK
  3144.       end
  3145.   end;
  3146.  
  3147. {!ckglob.p!}
  3148.   { ckglob -- if global prefix, mark lines to be affected }
  3149.   function CKGLOB(var LIN : STRINGZ;
  3150.                   var I : Integer;
  3151.                   var STATUS : STCODE) : STCODE;
  3152.   var
  3153.     N : Integer;
  3154.     GFLAG : Boolean;
  3155.     TEMP : STRINGZ;
  3156.   begin
  3157.     if ((LIN[I] <> GCMD) and
  3158.         (LIN[I] <> XCMD)) then
  3159.       STATUS := ENDDATA
  3160.     else
  3161.       begin
  3162.         GFLAG := (LIN[I] = GCMD);
  3163.         I := I+1;
  3164.         if (OPTPAT(LIN, I) = ERR) then
  3165.           STATUS := ERR
  3166.         else if (DEFAULT(1, LASTLN, STATUS) <> ERR) then
  3167.           begin
  3168.             I := I+1;           { mark affected lines }
  3169.             for N := LINE1 to LINE2 do
  3170.               begin
  3171.                 GETTXT(N, TEMP);
  3172.                 PUTMARK(N, (MATCH(TEMP, PAT) = GFLAG))
  3173.               end;
  3174.             for N := 1 to LINE1-1 do { erase other marks }
  3175.               PUTMARK(N, False);
  3176.             for N := LINE2+1 to LASTLN do
  3177.               PUTMARK(N, False);
  3178.             STATUS := OK
  3179.           end
  3180.       end;
  3181.     CKGLOB := STATUS
  3182.   end;
  3183.  
  3184. {!getmark.p!}
  3185.   { getmark -- get mark from nth line }
  3186.   function GETMARK(N : Integer) : Boolean;
  3187.   begin
  3188.     GETMARK := BUF[N].MARKZ
  3189.   end;
  3190.  
  3191. {!putmark.p!}
  3192.   { putmark -- put mark m on nth line }
  3193.   procedure PUTMARK(N : Integer;
  3194.                     M : Boolean);
  3195.   begin
  3196.     BUF[N].MARKZ := M
  3197.   end;
  3198.  
  3199. {!doglob.p!}
  3200.   { doglob -- do command at lin[i] on all marked lines }
  3201.   function DOGLOB(var LIN : STRINGZ;
  3202.                   var I, CURSAVE : Integer;
  3203.                   var STATUS : STCODE) : STCODE;
  3204.   var
  3205.     COUNT, ISTART, N : Integer;
  3206.   begin
  3207.     STATUS := OK;
  3208.     COUNT := 0;
  3209.     N := LINE1;
  3210.     ISTART := I;
  3211.     repeat
  3212.       if (GETMARK(N)) then
  3213.         begin
  3214.           PUTMARK(N, False);
  3215.           CURLN := N;
  3216.           CURSAVE := CURLN;
  3217.           I := ISTART;
  3218.           if (DOCMD(LIN, I, True, STATUS) = OK) then
  3219.             COUNT := 0
  3220.         end
  3221.       else
  3222.         begin
  3223.           N := NEXTLN(N);
  3224.           COUNT := COUNT+1
  3225.         end
  3226.     until ((COUNT > LASTLN) or
  3227.            (STATUS <> OK));
  3228.     DOGLOB := STATUS
  3229.   end;
  3230.  
  3231. {!docmd.p!}
  3232.   { docmd -- handle all commands except globals }
  3233.   function DOCMD(var LIN : STRINGZ;
  3234.                  var I : Integer;
  3235.                  GLOB : Boolean;
  3236.                  var STATUS : STCODE) : STCODE;
  3237.   var
  3238.     FIL, SUB : STRINGZ;
  3239.     LINE3 : Integer;
  3240.     GFLAG, PFLAG : Boolean;
  3241.   begin
  3242.     PFLAG := False;             { may be set by d, m, s }
  3243.     STATUS := ERR;
  3244.     if (LIN[I] = PCMD) then
  3245.       begin
  3246.         if (LIN[I+1] = NEWLINE) then
  3247.           if (DEFAULT(CURLN, CURLN, STATUS) = OK) then
  3248.             STATUS := DOPRINT(LINE1, LINE2)
  3249.       end
  3250.     else if (LIN[I] = NEWLINE) then
  3251.       begin
  3252.         if (NLINES = 0) then
  3253.           LINE2 := NEXTLN(CURLN);
  3254.         STATUS := DOPRINT(LINE2, LINE2)
  3255.       end
  3256.     else if (LIN[I] = QCMD) then
  3257.       begin
  3258.         if ((LIN[I+1] = NEWLINE) and
  3259.             (NLINES = 0) and
  3260.             (not GLOB)) then
  3261.           STATUS := ENDDATA
  3262.       end
  3263.     else if (LIN[I] = ACMD) then
  3264.       begin
  3265.         if (LIN[I+1] = NEWLINE) then
  3266.           STATUS := APPENDZ(LINE2, GLOB)
  3267.       end
  3268.     else if (LIN[I] = CCMD) then
  3269.       begin
  3270.         if (LIN[I+1] = NEWLINE) then
  3271.           if (DEFAULT(CURLN, CURLN, STATUS) = OK) then
  3272.             if (LNDELETE(LINE1, LINE2, STATUS) = OK) then
  3273.               STATUS := APPENDZ(PREVLN(LINE1), GLOB)
  3274.       end
  3275.     else if (LIN[I] = DCMD) then
  3276.       begin
  3277.         if (CKP(LIN, I+1, PFLAG, STATUS) = OK) then
  3278.           if (DEFAULT(CURLN, CURLN, STATUS) = OK) then
  3279.             if (LNDELETE(LINE1, LINE2, STATUS) = OK) then
  3280.               if (NEXTLN(CURLN) <> 0) then
  3281.                 CURLN := NEXTLN(CURLN)
  3282.       end
  3283.     else if (LIN[I] = ICMD) then
  3284.       begin
  3285.         if (LIN[I+1] = NEWLINE) then
  3286.           begin
  3287.             if (LINE2 = 0) then
  3288.               STATUS := APPENDZ(0, GLOB)
  3289.             else
  3290.               STATUS := APPENDZ(PREVLN(LINE2), GLOB)
  3291.           end
  3292.       end
  3293.     else if (LIN[I] = EQCMD) then
  3294.       begin
  3295.         if (CKP(LIN, I+1, PFLAG, STATUS) = OK) then
  3296.           begin
  3297.             PUTDEC(LINE2, 1);
  3298.             PUTC(NEWLINE)
  3299.           end
  3300.       end
  3301.     else if (LIN[I] = MCMD) then
  3302.       begin
  3303.         I := I+1;
  3304.         if (GETONE(LIN, I, LINE3, STATUS) = ENDDATA) then
  3305.           STATUS := ERR;
  3306.         if (STATUS = OK) then
  3307.           if (CKP(LIN, I, PFLAG, STATUS) = OK) then
  3308.             if (DEFAULT(CURLN, CURLN, STATUS) = OK) then
  3309.               STATUS := MOVEZ(LINE3)
  3310.       end
  3311.     else if (LIN[I] = SCMD) then
  3312.       begin
  3313.         I := I+1;
  3314.         if (OPTPAT(LIN, I) = OK) then
  3315.           if (GETRHS(LIN, I, SUB, GFLAG) = OK) then
  3316.             if (CKP(LIN, I+1, PFLAG, STATUS) = OK) then
  3317.               if (DEFAULT(CURLN, CURLN, STATUS) = OK) then
  3318.                 STATUS := SUBST(SUB, GFLAG, GLOB)
  3319.       end
  3320.     else if (LIN[I] = ECMD) then
  3321.       begin
  3322.         if (NLINES = 0) then
  3323.           if (GETFN(LIN, I, FIL) = OK) then
  3324.             begin
  3325.               SCOPY(FIL, 1, SAVEFILE, 1);
  3326.               CLRBUF;
  3327.               SETBUF;
  3328.               STATUS := DOREAD(0, FIL)
  3329.             end
  3330.       end
  3331.     else if (LIN[I] = FCMD) then
  3332.       begin
  3333.         if (NLINES = 0) then
  3334.           if (GETFN(LIN, I, FIL) = OK) then
  3335.             begin
  3336.               SCOPY(FIL, 1, SAVEFILE, 1);
  3337.               PUTSTR(SAVEFILE, STDOUT);
  3338.               PUTC(NEWLINE);
  3339.               STATUS := OK
  3340.             end
  3341.       end
  3342.     else if (LIN[I] = RCMD) then
  3343.       begin
  3344.         if (GETFN(LIN, I, FIL) = OK) then
  3345.           STATUS := DOREAD(LINE2, FIL)
  3346.       end
  3347.     else if (LIN[I] = WCMD) then
  3348.       begin
  3349.         if (GETFN(LIN, I, FIL) = OK) then
  3350.           if (DEFAULT(1, LASTLN, STATUS) = OK) then
  3351.             STATUS := DOWRITE(LINE1, LINE2, FIL)
  3352.       end;
  3353.     { else status is ERR }
  3354.     if ((STATUS = OK) and
  3355.         (PFLAG)) then
  3356.       STATUS := DOPRINT(CURLN, CURLN);
  3357.     DOCMD := STATUS
  3358.   end;
  3359.  
  3360. {!edit1.p!}
  3361.   { edit -- main routine for text editor }
  3362.   procedure EDIT;
  3363.  
  3364. #include "editcons.p"
  3365. #include "edittyp1.p"
  3366. #include "editvar1.p"
  3367.     CURSAVE, I : Integer;
  3368.     STATUS : STCODE;
  3369.     MORE : Boolean;
  3370.  
  3371. #include "editpro1.p"
  3372.   begin
  3373.     SETBUF;
  3374.     PAT[1] := ENDSTR;
  3375.     SAVEFILE[1] := ENDSTR;
  3376.     if (GETARG(1, SAVEFILE, MAXSTR)) then
  3377.       if (DOREAD(0, SAVEFILE) = ERR) then
  3378.         MESSAGE('?');
  3379.     MORE := GETLINE(LIN, STDIN, MAXSTR);
  3380.     while (MORE) do
  3381.       begin
  3382.         I := 1;
  3383.         CURSAVE := CURLN;
  3384.         if (GETLIST(LIN, I, STATUS) = OK) then
  3385.           begin
  3386.             if (CKGLOB(LIN, I, STATUS) = OK) then
  3387.               STATUS := DOGLOB(LIN, I, CURSAVE, STATUS)
  3388.             else if (STATUS <> ERR) then
  3389.               STATUS := DOCMD(LIN, I, False, STATUS)
  3390.                         { else ERR, do nothing }
  3391.           end;
  3392.         if (STATUS = ERR) then
  3393.           begin
  3394.             MESSAGE('?');
  3395.             CURLN := MIN(CURSAVE, LASTLN)
  3396.           end
  3397.         else if (STATUS = ENDDATA) then
  3398.           MORE := False;
  3399.         { else OK }
  3400.         if (MORE) then
  3401.           MORE := GETLINE(LIN, STDIN, MAXSTR)
  3402.       end;
  3403.     CLRBUF
  3404.   end;
  3405.  
  3406. {!editvar1.p!}
  3407.   { editvar -- (in-memory) variables for edit }
  3408. var
  3409.   BUF : array[0..MAXLINES] of BUFTYPE;
  3410.  
  3411.   LINE1 : Integer;              { first line number }
  3412.   LINE2 : Integer;              { second line number }
  3413.   NLINES : Integer;             { # of line numbers specified }
  3414.   CURLN : Integer;              { current line - value of dot }
  3415.   LASTLN : Integer;             { last line - value of $ }
  3416.  
  3417.   PAT : STRINGZ;                { pattern }
  3418.   LIN : STRINGZ;                { input line }
  3419.   SAVEFILE : STRINGZ;           { remembered file name }
  3420.  
  3421. {!editpro1.p!}
  3422.   { editproc -- procedures for edit }
  3423.  
  3424. #include "edprim1.p"            {editor buffer primitives }
  3425. #include "amatch.p"
  3426. #include "match.p"
  3427. #include "skipbl.p"
  3428. #include "optpat.p"
  3429. #include "nextln.p"
  3430. #include "prevln.p"
  3431. #include "patscan.p"
  3432. #include "getnum.p"
  3433. #include "getone.p"
  3434. #include "getlist.p"
  3435. #include "appendz.p"
  3436. #include "lndelete.p"
  3437. #include "doprint.p"
  3438. #include "doread.p"
  3439. #include "dowrite.p"
  3440. #include "movez.p"
  3441. #include "makesub.p"
  3442. #include "getrhs.p"
  3443. #include "catsub.p"
  3444. #include "subst.p"
  3445. #include "ckp.p"
  3446. #include "default.p"
  3447. #include "getfn.p"
  3448. #include "docmd.p"
  3449. #include "ckglob.p"
  3450. #include "doglob.p"
  3451.  
  3452. {!edprim1.p!}
  3453.   { edprim -- editor buffer primitives }
  3454.  
  3455. #include "setbuf1.p"
  3456. #include "clrbuf1.p"
  3457. #include "reverse.p"
  3458. #include "blkmove.p"
  3459. #include "putmark.p"
  3460. #include "getmark.p"
  3461. #include "puttxt1.p"
  3462. #include "gettxt1.p"
  3463.  
  3464. {!editcons.p!}
  3465.   { editcons -- const declarations for edit }
  3466. const
  3467.   MAXLINES = 100;               { set small for testing }
  3468.   MAXPAT = MAXSTR;
  3469.   CLOSIZE = 1;                  { size of a closure entry }
  3470.   DITTO = 255;                  {TP7}
  3471.   CLOSURE = STAR;
  3472.   BOL = PERCENT;
  3473.   EOL = DOLLAR;
  3474.   ANY = QUESTION;
  3475.   CCL = LBRACK;
  3476.   CCLEND = RBRACK;
  3477.   NEGATE = CARET;
  3478.   NCCL = EXCLAM;
  3479.   LITCHAR = Ord('c');
  3480.   CURLINE = PERIOD;
  3481.   LASTLINE = DOLLAR;
  3482.   SCAN = Ord('/');
  3483.   BACKSCAN = Ord('\');
  3484.  
  3485.   ACMD = Ord('a');
  3486.   CCMD = Ord('c');
  3487.   DCMD = Ord('d');
  3488.   ECMD = Ord('e');
  3489.   EQCMD = EQUALS;
  3490.   FCMD = Ord('f');
  3491.   GCMD = Ord('g');
  3492.   ICMD = Ord('i');
  3493.   MCMD = Ord('m');
  3494.   PCMD = Ord('p');
  3495.   QCMD = Ord('q');
  3496.   RCMD = Ord('r');
  3497.   SCMD = Ord('s');
  3498.   WCMD = Ord('w');
  3499.   XCMD = Ord('x');
  3500.  
  3501. {!edittyp1.p!}
  3502.   { edittype -- types for in-memory version of edit }
  3503. type
  3504.   STCODE = (ENDDATA, ERR, OK);  { status returns }
  3505.   BUFTYPE = record              { in-memory edit buffer entry }
  3506.               TXT : STRINGZ;    { text of line }
  3507.               MARKZ : Boolean;  { mark for line }
  3508.             end;
  3509.  
  3510. {!edprim2.p!}
  3511.   { edprim -- (scratch file) editor buffer primitives }
  3512.  
  3513. #include "setbuf2.p"
  3514. #include "clrbuf2.p"
  3515. #include "reverse.p"
  3516. #include "blkmove.p"
  3517. #include "putmark.p"
  3518. #include "getmark.p"
  3519. #include "puttxt2.p"
  3520. #include "gettxt2.p"
  3521.  
  3522. {!edittyp2.p!}
  3523.   { edittype -- types for scratch-file of edit }
  3524. type
  3525.   STCODE = (ENDDATA, ERR, OK);
  3526.   BUFTYPE = record
  3527.               TXT : Integer;    { text of line }
  3528.               MARKZ : Boolean;  { mark for line }
  3529.             end;
  3530.  
  3531. {!editvar2.p!}
  3532.   { editvar -- (scratch file) variables for edit }
  3533. var
  3534.   BUF : array[0..MAXLINES] of BUFTYPE;
  3535.  
  3536.   LINE1 : Integer;              { first line number }
  3537.   LINE2 : Integer;              { second line number }
  3538.   NLINES : Integer;             { # of line numbers specified }
  3539.   CURLN : Integer;              { current line - value of dot }
  3540.   LASTLN : Integer;             { last line - value of $ }
  3541.  
  3542.   PAT : STRINGZ;                { pattern }
  3543.   LIN : STRINGZ;                { input line }
  3544.   SAVEFILE : STRINGZ;           { remembered file name }
  3545.  
  3546.   SCROUT : FILEDESC;            { scratch input fd }
  3547.   SCRIN : FILEDESC;             { scratch output fd }
  3548.   RECIN : Integer;              { next record to read from scrin }
  3549.   RECOUT : Integer;             { next record to write on scrout }
  3550.   EDITTEMP : STRINGZ;           { temp file name 'edtemp' }
  3551.  
  3552. {!puttxt2.p!}
  3553.   { puttxt -- (scratch file) put text from lin after curln }
  3554.   function PUTTXT(var LIN : STRINGZ) : STCODE;
  3555.   begin
  3556.     PUTTXT := ERR;
  3557.     if (LASTLN < MAXLINES) then
  3558.       begin
  3559.         LASTLN := LASTLN+1;
  3560.         PUTSTR(LIN, SCROUT);
  3561.         PUTMARK(LASTLN, False);
  3562.         BUF[LASTLN].TXT := RECOUT;
  3563.         RECOUT := RECOUT+1;
  3564.         BLKMOVE(LASTLN, LASTLN, CURLN);
  3565.         CURLN := CURLN+1;
  3566.         PUTTXT := OK
  3567.       end
  3568.   end;
  3569.  
  3570. {!gettxt2.p!}
  3571.   { gettxt -- (scratch file) get text from line n into s }
  3572.   procedure GETTXT(N : Integer;
  3573.                    var S : STRINGZ);
  3574.   var
  3575.     JUNK : Boolean;
  3576.  
  3577. #include "seekz.p"
  3578.   begin
  3579.     if (N = 0) then
  3580.       S[1] := ENDSTR
  3581.     else
  3582.       begin
  3583.         SEEKZ(BUF[N].TXT, SCRIN);
  3584.         RECIN := RECIN+1;
  3585.         JUNK := GETLINE(S, SCRIN, MAXSTR)
  3586.       end
  3587.   end;
  3588.  
  3589. {!setbuf2.p!}
  3590.   { setbuf -- (scratch file) create scratch file, set up line 0 }
  3591.   procedure SETBUF;
  3592.   begin
  3593.     { setstring(edittemp, 'edtemp'); }
  3594.     EDITTEMP[1] := Ord('e');
  3595.     EDITTEMP[2] := Ord('d');
  3596.     EDITTEMP[3] := Ord('t');
  3597.     EDITTEMP[4] := Ord('e');
  3598.     EDITTEMP[5] := Ord('m');
  3599.     EDITTEMP[6] := Ord('p');
  3600.     EDITTEMP[7] := ENDSTR;
  3601.     SCROUT := MUSTCREATE(EDITTEMP, IOWRITE);
  3602.     SCRIN := MUSTOPEN(EDITTEMP, IOREAD);
  3603.     RECOUT := 1;
  3604.     RECIN := 1;
  3605.     CURLN := 0;
  3606.     LASTLN := 0;
  3607.   end;
  3608.  
  3609. {!clrbuf2.p!}
  3610.   { clrbuf -- (scratch file) initialize for new file }
  3611.   procedure CLRBUF;
  3612.   begin
  3613.     CLOSEZ(SCRIN);
  3614.     CLOSEZ(SCROUT);
  3615.     REMOVE(EDITTEMP)
  3616.   end;
  3617.  
  3618. {!edit2.p!}
  3619.   { edit -- main routine for text editor }
  3620.   procedure EDIT;
  3621.  
  3622. #include "editcons.p"
  3623. #include "edittyp2.p"
  3624. #include "editvar2.p"
  3625.     CURSAVE, I : Integer;
  3626.     STATUS : STCODE;
  3627.     MORE : Boolean;
  3628.  
  3629. #include "editpro2.p"
  3630.   begin
  3631.     SETBUF;
  3632.     PAT[1] := ENDSTR;
  3633.     SAVEFILE[1] := ENDSTR;
  3634.     if (GETARG(1, SAVEFILE, MAXSTR)) then
  3635.       if (DOREAD(0, SAVEFILE) = ERR) then
  3636.         MESSAGE('?');
  3637.     MORE := GETLINE(LIN, STDIN, MAXSTR);
  3638.     while (MORE) do
  3639.       begin
  3640.         I := 1;
  3641.         CURSAVE := CURLN;
  3642.         if (GETLIST(LIN, I, STATUS) = OK) then
  3643.           begin
  3644.             if (CKGLOB(LIN, I, STATUS) = OK) then
  3645.               STATUS := DOGLOB(LIN, I, CURSAVE, STATUS)
  3646.             else if (STATUS <> ERR) then
  3647.               STATUS := DOCMD(LIN, I, False, STATUS)
  3648.                         { else ERR, do nothing }
  3649.           end;
  3650.         if (STATUS = ERR) then
  3651.           begin
  3652.             MESSAGE('?');
  3653.             CURLN := MIN(CURSAVE, LASTLN)
  3654.           end
  3655.         else if (STATUS = ENDDATA) then
  3656.           MORE := False;
  3657.         { else OK }
  3658.         if (MORE) then
  3659.           MORE := GETLINE(LIN, STDIN, MAXSTR)
  3660.       end;
  3661.     CLRBUF
  3662.   end;
  3663.  
  3664. {!editpro2.p!}
  3665.   { editproc -- procedures for edit }
  3666.  
  3667. #include "edprim2.p"            {editor buffer primitives }
  3668. #include "amatch.p"
  3669. #include "match.p"
  3670. #include "skipbl.p"
  3671. #include "optpat.p"
  3672. #include "nextln.p"
  3673. #include "prevln.p"
  3674. #include "patscan.p"
  3675. #include "getnum.p"
  3676. #include "getone.p"
  3677. #include "getlist.p"
  3678. #include "appendz.p"
  3679. #include "lndelete.p"
  3680. #include "doprint.p"
  3681. #include "doread.p"
  3682. #include "dowrite.p"
  3683. #include "movez.p"
  3684. #include "makesub.p"
  3685. #include "getrhs.p"
  3686. #include "catsub.p"
  3687. #include "subst.p"
  3688. #include "ckp.p"
  3689. #include "default.p"
  3690. #include "getfn.p"
  3691. #include "docmd.p"
  3692. #include "ckglob.p"
  3693. #include "doglob.p"
  3694.  
  3695. {!command.p!}
  3696.   { command -- perform formatting command }
  3697.   procedure COMMAND(var BUF : STRINGZ);
  3698.   var
  3699.     CMD : CMDTYPE;
  3700.     ARGTYPE, SPVAL, VALZ : Integer;
  3701.   begin
  3702.     CMD := GETCMD(BUF);
  3703.     if (CMD <> UNKNOWN) then
  3704.       VALZ := GETVAL(BUF, ARGTYPE);
  3705.     case CMD of
  3706.       FI :
  3707.         begin
  3708.           BREAKZ;
  3709.           FILL := True
  3710.         end;
  3711.       NF :
  3712.         begin
  3713.           BREAKZ;
  3714.           FILL := False
  3715.         end;
  3716.       BR :
  3717.         BREAKZ;
  3718.       LS :
  3719.         SETPARAM(LSVAL, VALZ, ARGTYPE, 1, 1, HUGE);
  3720.       CE :
  3721.         begin
  3722.           BREAKZ;
  3723.           SETPARAM(CEVAL, VALZ, ARGTYPE, 1, 0, HUGE)
  3724.         end;
  3725.       UL :
  3726.         SETPARAM(ULVAL, VALZ, ARGTYPE, 1, 0, HUGE);
  3727.       HE :
  3728.         GETTL(BUF, HEADER);
  3729.       FO :
  3730.         GETTL(BUF, FOOTER);
  3731.       BP :
  3732.         begin
  3733.           PAGE;
  3734.           SETPARAM(CURPAGE, VALZ, ARGTYPE, CURPAGE+1, -HUGE, HUGE);
  3735.           NEWPAGE := CURPAGE
  3736.         end;
  3737.       SP :
  3738.         begin
  3739.           SETPARAM(SPVAL, VALZ, ARGTYPE, 1, 0, HUGE);
  3740.           SPACE(SPVAL)
  3741.         end;
  3742.       IND :
  3743.         SETPARAM(INVAL, VALZ, ARGTYPE, 0, 0, RMVAL-1);
  3744.       RM :
  3745.         SETPARAM(INVAL, VALZ, ARGTYPE, PAGEWIDTH, INVAL+TIVAL+1, HUGE);
  3746.       TI :
  3747.         begin
  3748.           BREAKZ;
  3749.           SETPARAM(TIVAL, VALZ, ARGTYPE, 0, -HUGE, RMVAL)
  3750.         end;
  3751.       PL :
  3752.         begin
  3753.           SETPARAM(PLVAL, VALZ, ARGTYPE, PAGELEN,
  3754.                    M1VAL+M2VAL+M3VAL+M4VAL+1, HUGE);
  3755.           BOTTOM := PLVAL-M3VAL-M4VAL
  3756.         end;
  3757.       UNKNOWN :
  3758.         { ignore }
  3759.     end
  3760.   end;
  3761.  
  3762. {!getcmd.p!}
  3763.   { getcmd -- decode command type }
  3764.   function GETCMD(var BUF : STRINGZ) : CMDTYPE;
  3765.   var
  3766.     CMD : packed array[1..2] of Char;
  3767.   begin
  3768.     CMD[1] := Chr(BUF[2]);
  3769.     CMD[2] := Chr(BUF[3]);
  3770.     if (CMD = 'fi') then
  3771.       GETCMD := FI
  3772.     else if (CMD = 'nf') then
  3773.       GETCMD := NF
  3774.     else if (CMD = 'br') then
  3775.       GETCMD := BR
  3776.     else if (CMD = 'ls') then
  3777.       GETCMD := LS
  3778.     else if (CMD = 'bp') then
  3779.       GETCMD := BP
  3780.     else if (CMD = 'sp') then
  3781.       GETCMD := SP
  3782.     else if (CMD = 'in') then
  3783.       GETCMD := IND
  3784.     else if (CMD = 'rm') then
  3785.       GETCMD := RM
  3786.     else if (CMD = 'ce') then
  3787.       GETCMD := CE
  3788.     else if (CMD = 'ti') then
  3789.       GETCMD := TI
  3790.     else if (CMD = 'ul') then
  3791.       GETCMD := UL
  3792.     else if (CMD = 'he') then
  3793.       GETCMD := HE
  3794.     else if (CMD = 'fo') then
  3795.       GETCMD := FO
  3796.     else if (CMD = 'pl') then
  3797.       GETCMD := PL
  3798.     else
  3799.       GETCMD := UNKNOWN
  3800.   end;
  3801.  
  3802. {!getval.p!}
  3803.   { getval -- evaluate optional numeric argument }
  3804.   function GETVAL(var BUF : STRINGZ;
  3805.                   var ARGTYPE : Integer) : Integer;
  3806.   var
  3807.     I : Integer;
  3808.   begin
  3809.     I := 1;                     { skip over command name }
  3810.     while (not(BUF[I] in [BLANK, TAB, NEWLINE])) do
  3811.       I := I+1;
  3812.     SKIPBL(BUF, I);             { find argument }
  3813.     ARGTYPE := BUF[I];
  3814.     if ((ARGTYPE = PLUS) or
  3815.         (ARGTYPE = MINUS)) then
  3816.       I := I+1;
  3817.     GETVAL := CTOI(BUF, I)
  3818.   end;
  3819.  
  3820. {!setparam.p!}
  3821.   { setparam -- set parameter and check range }
  3822.   procedure SETPARAM(var PARAM : Integer;
  3823.                      VALZ, ARGTYPE, DEFVAL, MINVAL, MAXVAL : Integer);
  3824.   begin
  3825.     if (ARGTYPE = NEWLINE) then { defaulted }
  3826.       PARAM := DEFVAL
  3827.     else if (ARGTYPE = PLUS) then { relative + }
  3828.       PARAM := PARAM+VALZ
  3829.     else if (ARGTYPE = MINUS) then { relative - }
  3830.       PARAM := PARAM-VALZ
  3831.     else                        { absolute }
  3832.       PARAM := VALZ;
  3833.     PARAM := MIN(PARAM, MAXVAL);
  3834.     PARAM := MAX(PARAM, MINVAL)
  3835.   end;
  3836.  
  3837. {!textz1.p!}
  3838.   { textz -- process text lines (interim version 1) }
  3839.   procedure TEXTZ(var INBUF : STRINGZ);
  3840.   begin
  3841.     PUT(INBUF)
  3842.   end;
  3843.  
  3844. {!put.p!}
  3845.   { put -- put out line with proper spacing and indenting }
  3846.   procedure PUT(var BUF : STRINGZ);
  3847.   var
  3848.     I : Integer;
  3849.   begin
  3850.     if ((LINENO <= 0) or
  3851.         (LINENO > BOTTOM)) then
  3852.       PUTHEAD;
  3853.     for I := 1 to INVAL+TIVAL do { indenting }
  3854.       PUTC(BLANK);
  3855.     TIVAL := 0;
  3856.     PUTSTR(BUF, STDOUT);
  3857.     SKIP(MIN(LSVAL-1, BOTTOM-LINENO));
  3858.     LINENO := LINENO+LSVAL;
  3859.     if (LINENO > BOTTOM) then
  3860.       PUTFOOT
  3861.   end;
  3862.  
  3863. {!puthead.p!}
  3864.   { puthead -- put out page header }
  3865.   procedure PUTHEAD;
  3866.   begin
  3867.     CURPAGE := NEWPAGE;
  3868.     NEWPAGE := NEWPAGE+1;
  3869.     if (M1VAL > 0) then
  3870.       begin
  3871.         SKIP(M1VAL-1);
  3872.         PUTTL(HEADER, CURPAGE)
  3873.       end;
  3874.     SKIP(M2VAL);
  3875.     LINENO := M1VAL+M2VAL+1
  3876.   end;
  3877.  
  3878. {!putfoot.p!}
  3879.   { putfoot -- put out page footer }
  3880.   procedure PUTFOOT;
  3881.   begin
  3882.     SKIP(M3VAL);
  3883.     if (M4VAL > 0) then
  3884.       begin
  3885.         PUTTL(FOOTER, CURPAGE);
  3886.         SKIP(M4VAL-1)
  3887.       end
  3888.   end;
  3889.  
  3890. {!puttl.p!}
  3891.   { puttl -- put out title line with optional page number }
  3892.   procedure PUTTL(var BUF : STRINGZ;
  3893.                   PAGENO : Integer);
  3894.   var
  3895.     I : Integer;
  3896.   begin
  3897.     for I := 1 to LENGTHZ(BUF) do
  3898.       if (BUF[I] = PAGENUM) then
  3899.         PUTDEC(PAGENO, 1)
  3900.       else
  3901.         PUTC(BUF[I])
  3902.   end;
  3903.  
  3904. {!gettl.p!}
  3905.   { gettl -- copy title from buf to ttl }
  3906.   procedure GETTL(var BUF, TTL : STRINGZ);
  3907.   var
  3908.     I : Integer;
  3909.   begin
  3910.     I := 1;                     { skip command name }
  3911.     while (not(BUF[I] in [BLANK, TAB, NEWLINE])) do
  3912.       I := I+1;
  3913.     SKIPBL(BUF, I);             { find argument }
  3914.     if (BUF[I] = SQUOTE) or (BUF[I] = DQUOTE) then
  3915.       I := I+1;                 { strip leading quote }
  3916.     SCOPY(BUF, I, TTL, 1)
  3917.   end;
  3918.  
  3919. {!space.p!}
  3920.   { space -- space n lines or to bottom of page }
  3921.   procedure SPACE(N : Integer);
  3922.   begin
  3923.     BREAKZ;
  3924.     if (LINENO <= BOTTOM) then
  3925.       begin
  3926.         if (LINENO <= 0) then
  3927.           PUTHEAD;
  3928.         SKIP(MIN(N, BOTTOM+1-LINENO));
  3929.         LINENO := LINENO+N;
  3930.         if (LINENO > BOTTOM) then
  3931.           PUTFOOT
  3932.       end
  3933.   end;
  3934.  
  3935. {!page.p!}
  3936.   { page -- get to top of new page }
  3937.   procedure PAGE;
  3938.   begin
  3939.     BREAKZ;
  3940.     if ((LINENO > 0) and
  3941.         (LINENO <= BOTTOM)) then
  3942.       begin
  3943.         SKIP(BOTTOM+1-LINENO);
  3944.         PUTFOOT
  3945.       end;
  3946.     LINENO := 0
  3947.   end;
  3948.  
  3949. {!leadbl.p!}
  3950.   { leadbl -- delete leading blanks, set tival }
  3951.   procedure LEADBL(var BUF : STRINGZ);
  3952.   var
  3953.     I, J : Integer;
  3954.   begin
  3955.     BREAKZ;
  3956.     I := 1;
  3957.     while (BUF[I] = BLANK) do   { find 1st non-blank }
  3958.       I := I+1;
  3959.     if (BUF[I] <> NEWLINE) then
  3960.       TIVAL := TIVAL+I-1;
  3961.     for J := I to LENGTHZ(BUF)+1 do { move line to left }
  3962.       BUF[J-I+1] := BUF[J]
  3963.   end;
  3964.  
  3965. {!textz2.p!}
  3966.   { textz -- process text lines (interim version 2) }
  3967.   procedure TEXTZ(var INBUF : STRINGZ);
  3968.   var
  3969.     WORDBUF : STRINGZ;
  3970.     I : Integer;
  3971.   begin
  3972.     if ((INBUF[1] = BLANK) or
  3973.         (INBUF[1] = NEWLINE)) then
  3974.       LEADBL(INBUF);            { move left, set tival }
  3975.     if (INBUF[1] = NEWLINE) then { all blank line }
  3976.       PUT(INBUF)
  3977.     else if (not FILL) then     { unfilled text }
  3978.       PUT(INBUF)
  3979.     else
  3980.       begin                     { filled text }
  3981.         I := 1;
  3982.         repeat
  3983.           I := GETWORD(INBUF, I, WORDBUF);
  3984.           if (I > 0) then
  3985.             PUTWORD(WORDBUF)
  3986.         until (I = 0)
  3987.       end
  3988.   end;
  3989.  
  3990. {!putword1.p!}
  3991.   { putword -- put word in outbuf }
  3992.   procedure PUTWORD(var WORDBUF : STRINGZ);
  3993.   var
  3994.     LAST, LLVAL, NEXTRA, W : Integer;
  3995.   begin
  3996.     W := WIDTH(WORDBUF);
  3997.     LAST := LENGTHZ(WORDBUF)+OUTP+1; { new end of outbuf }
  3998.     LLVAL := RMVAL-TIVAL-INVAL;
  3999.     if ((OUTP > 0) and
  4000.         ((OUTW+W > LLVAL) or
  4001.          (LAST >= MAXSTR))) then
  4002.       begin
  4003.         LAST := LAST-OUTP;      { remember end of wordbuf }
  4004.         BREAKZ                  { flush previous line }
  4005.       end;
  4006.     SCOPY(WORDBUF, 1, OUTBUF, OUTP+1);
  4007.     OUTP := LAST;
  4008.     OUTBUF[OUTP] := BLANK;      { blank between words }
  4009.     OUTW := OUTW+W+1;           { 1 for blank }
  4010.     OUTWDS := OUTWDS+1
  4011.   end;
  4012.  
  4013. {!width.p!}
  4014.   { width -- compute width of character string }
  4015.   function WIDTH(var BUF : STRINGZ) : Integer;
  4016.   var
  4017.     I, W : Integer;
  4018.   begin
  4019.     W := 0;
  4020.     I := 1;
  4021.     while (BUF[I] <> ENDSTR) do
  4022.       begin
  4023.         if (BUF[I] = BACKSPACE) then
  4024.           W := W-1
  4025.         else if (BUF[I] <> NEWLINE) then
  4026.           W := W+1;
  4027.         I := I+1
  4028.       end;
  4029.     WIDTH := W
  4030.   end;
  4031.  
  4032. {!breakz.p!}
  4033.   { breakz -- end current filled line }
  4034.   procedure BREAKZ;
  4035.   begin
  4036.     if (OUTP > 0) then
  4037.       begin
  4038.         OUTBUF[OUTP] := NEWLINE;
  4039.         OUTBUF[OUTP+1] := ENDSTR;
  4040.         PUT(OUTBUF)
  4041.       end;
  4042.     OUTP := 0;
  4043.     OUTW := 0;
  4044.     OUTWDS := 0
  4045.   end;
  4046.  
  4047. {!putword.p!}
  4048.   { putword -- put word in outbuf, does margin justification }
  4049.   procedure PUTWORD(var WORDBUF : STRINGZ);
  4050.   var
  4051.     LAST, LLVAL, NEXTRA, W : Integer;
  4052.   begin
  4053.     W := WIDTH(WORDBUF);
  4054.     LAST := LENGTHZ(WORDBUF)+OUTP+1;
  4055.     LLVAL := RMVAL-TIVAL-INVAL;
  4056.     if ((OUTP > 0) and
  4057.         ((OUTW+W > LLVAL) or
  4058.          (LAST >= MAXSTR))) then
  4059.       begin
  4060.         LAST := LAST-OUTP;      { remember end of wordbuf }
  4061.         NEXTRA := LLVAL-OUTW+1;
  4062.         if ((NEXTRA > 0) and
  4063.             (OUTWDS > 1)) then
  4064.           begin
  4065.             SPREAD(OUTBUF, OUTP, NEXTRA, OUTWDS);
  4066.             OUTP := OUTP+NEXTRA
  4067.           end;
  4068.         BREAKZ                  { flush previous line }
  4069.       end;
  4070.     SCOPY(WORDBUF, 1, OUTBUF, OUTP+1);
  4071.     OUTP := LAST;
  4072.     OUTBUF[OUTP] := BLANK;      { blank between words }
  4073.     OUTW := OUTW+W+1;           { 1 for blank }
  4074.     OUTWDS := OUTWDS+1
  4075.   end;
  4076.  
  4077. {!spread.p!}
  4078.   { spread -- spread words to justify right margin }
  4079.   procedure SPREAD(var BUF : STRINGZ;
  4080.                    OUTP, NEXTRA, OUTWDS : Integer);
  4081.   var
  4082.     I, J, NB, NHOLES : Integer;
  4083.   begin
  4084.     if ((NEXTRA > 0) and
  4085.         (OUTWDS > 1)) then
  4086.       begin
  4087.         DIR := 1-DIR;           { reverse previous direction }
  4088.         NHOLES := OUTWDS-1;
  4089.         I := OUTP-1;
  4090.         J := MIN(MAXSTR-2, I+NEXTRA); { room fore NEWLINE }
  4091.         while (I < J) do
  4092.           begin                 { end ENDSTR }
  4093.             BUF[J] := BUF[I];
  4094.             if (BUF[I] = BLANK) then
  4095.               begin
  4096.                 if (DIR = 0) then
  4097.                   NB := (NEXTRA-1) div NHOLES+1
  4098.                 else NB := NEXTRA div NHOLES;
  4099.                 NEXTRA := NEXTRA-NB;
  4100.                 NHOLES := NHOLES-1;
  4101.                 while (NB > 0) do
  4102.                   begin
  4103.                     J := J-1;
  4104.                     BUF[J] := BLANK;
  4105.                     NB := NB-1
  4106.                   end
  4107.               end;
  4108.             I := I-1;
  4109.             J := J-1
  4110.           end
  4111.       end
  4112.   end;
  4113.  
  4114. {!center.p!}
  4115.   { center -- center a line by setting tival }
  4116.   procedure CENTER(var BUF : STRINGZ);
  4117.   begin
  4118.     TIVAL := MAX((RMVAL+TIVAL-WIDTH(BUF)) div 2, 0)
  4119.   end;
  4120.  
  4121. {!underln.p!}
  4122.   { underln -- underline a line }
  4123.   procedure UNDERLN(var BUF : STRINGZ;
  4124.                     SIZE : Integer);
  4125.   var
  4126.     I, J : Integer;
  4127.     TBUF : STRINGZ;
  4128.   begin
  4129.     J := 1;                     { expand into tbuf }
  4130.     I := 1;
  4131.     while ((BUF[I] <> NEWLINE) and
  4132.            (J < SIZE-1)) do
  4133.       begin
  4134.         if (ISALPHANUM(BUF[I])) then
  4135.           begin
  4136.             TBUF[J] := UNDERLINE;
  4137.             TBUF[J+1] := BACKSPACE;
  4138.             J := J+2
  4139.           end;
  4140.         TBUF[J] := BUF[I];
  4141.         J := J+1;
  4142.         I := I+1
  4143.       end;
  4144.     TBUF[J] := NEWLINE;
  4145.     TBUF[J+1] := ENDSTR;
  4146.     SCOPY(TBUF, 1, BUF, 1)      { copy it back to buf }
  4147.   end;
  4148.  
  4149. {!textz.p!}
  4150.   { textz -- process text lines (final version) }
  4151.   procedure TEXTZ(var INBUF : STRINGZ);
  4152.   var
  4153.     WORDBUF : STRINGZ;
  4154.     I : Integer;
  4155.   begin
  4156.     if ((INBUF[1] = BLANK) or
  4157.         (INBUF[1] = NEWLINE)) then
  4158.       LEADBL(INBUF);            { move left, set tival }
  4159.     if (ULVAL > 0) then
  4160.       begin                     { underlining }
  4161.         UNDERLN(INBUF, MAXSTR);
  4162.         ULVAL := ULVAL-1
  4163.       end;
  4164.     if (CEVAL > 0) then
  4165.       begin                     { centering }
  4166.         CENTER(INBUF);
  4167.         PUT(INBUF);
  4168.         CEVAL := CEVAL-1
  4169.       end
  4170.     else if (INBUF[1] = NEWLINE) then { all-blank line }
  4171.       PUT(INBUF)
  4172.     else if (not FILL) then     { unfilled text }
  4173.       PUT(INBUF)
  4174.     else
  4175.       begin                     { filled text }
  4176.         I := 1;
  4177.         repeat
  4178.           I := GETWORD(INBUF, I, WORDBUF);
  4179.           if (I > 0) then
  4180.             PUTWORD(WORDBUF)
  4181.         until (I = 0)
  4182.       end
  4183.   end;
  4184.  
  4185. {!format.p!}
  4186.   { format -- text formatter main program (final version) }
  4187.   procedure FORMAT;
  4188.  
  4189. #include "fmtcons.p"
  4190.   type
  4191.     CMDTYPE = (BP, BR, CE, FI, FO, HE, IND, LS, NF, PL,
  4192.                RM, SP, TI, UL, UNKNOWN);
  4193.   var
  4194.     { page parameters }
  4195.     CURPAGE : Integer;          { current output page number; init=0 }
  4196.     NEWPAGE : Integer;          { next output page number; init=1 }
  4197.     LINENO : Integer;           { next line to be printed; init=0 }
  4198.     PLVAL : Integer;            { page length in lines; init=PAGELEN=66 }
  4199.     M1VAL : Integer;            { margin before and including header }
  4200.     M2VAL : Integer;            { margin after header }
  4201.     M3VAL : Integer;            { margin after last text line }
  4202.     M4VAL : Integer;            { bottom margin, including footer }
  4203.     BOTTOM : Integer;           { last line on page, =plval-m3val-m4val }
  4204.     HEADER : STRINGZ;           { top of page title; init=NEWLINE }
  4205.     FOOTER : STRINGZ;           { bottom of page title; init=NEWLINE }
  4206.  
  4207.     { global parameters }
  4208.     FILL : Boolean;             { fill if true; init=true }
  4209.     LSVAL : Integer;            { current line spacing; init=1 }
  4210.     SPVAL : Integer;            { # of lines to space }
  4211.     INVAL : Integer;            { current indent; >= 0; init=0 }
  4212.     RMVAL : Integer;            { right margin; init=PAGEWIDTH=60 }
  4213.     TIVAL : Integer;            { current temporary indent; init=0 }
  4214.     CEVAL : Integer;            { # of lines to center; init=0 }
  4215.     ULVAL : Integer;            { # of lines to underline; init=0 }
  4216.  
  4217.     { output area }
  4218.     OUTP : Integer;             { last char pos in outbuf; init=0 }
  4219.     OUTW : Integer;             { width of text in outbuf; init=0 }
  4220.     OUTWDS : Integer;           { number of words in outbuf; init=0 }
  4221.     OUTBUF : STRINGZ;           { lines to be filled collect here }
  4222.     DIR : 0..1;                 { direction for blank padding }
  4223.     INBUF : STRINGZ;            { input line }
  4224.  
  4225. #include "fmtproc.p"
  4226.   begin
  4227.     INITFMT;
  4228.     while (GETLINE(INBUF, STDIN, MAXSTR)) do
  4229.       if (INBUF[1] = CMD) then
  4230.         COMMAND(INBUF)
  4231.       else
  4232.         TEXTZ(INBUF);
  4233.     PAGE
  4234.   end;
  4235.  
  4236. {!initfmt.p!}
  4237.   { initfmt -- set format parameters to default values }
  4238.   procedure INITFMT;
  4239.   begin
  4240.     FILL := True;
  4241.     DIR := 0;
  4242.     INVAL := 0;
  4243.     RMVAL := PAGEWIDTH;
  4244.     TIVAL := 0;
  4245.     LSVAL := 1;
  4246.     SPVAL := 0;
  4247.     CEVAL := 0;
  4248.     ULVAL := 0;
  4249.     LINENO := 0;
  4250.     CURPAGE := 0;
  4251.     NEWPAGE := 1;
  4252.     PLVAL := PAGELEN;
  4253.     M1VAL := 3; M2VAL := 2; M3VAL := 2; M4VAL := 3;
  4254.     BOTTOM := PLVAL-M3VAL-M4VAL;
  4255.     HEADER[1] := NEWLINE;       { initial titles }
  4256.     HEADER[2] := ENDSTR;
  4257.     FOOTER[1] := NEWLINE;
  4258.     FOOTER[2] := ENDSTR;
  4259.     OUTP := 0;
  4260.     OUTW := 0;
  4261.     OUTWDS := 0
  4262.   end;
  4263.  
  4264. {!fmtcons.p!}
  4265.   { fmtcons -- constants for format }
  4266. const
  4267.   CMD = PERIOD;
  4268.   PAGENUM = SHARP;
  4269.   PAGEWIDTH = 60;
  4270.   PAGELEN = 66;
  4271.   HUGE = 10000;
  4272.  
  4273. {!fmtproc.p!}
  4274.   { fmtproc -- procedures needed for format }
  4275.  
  4276. #include "skipbl.p"
  4277. #include "skip.p"
  4278. #include "getcmd.p"
  4279. #include "setparam.p"
  4280. #include "getval.p"
  4281. #include "gettl.p"
  4282. #include "puttl.p"
  4283. #include "puthead.p"
  4284. #include "putfoot.p"
  4285. #include "width.p"
  4286. #include "put.p"
  4287. #include "breakz.p"
  4288. #include "space.p"
  4289. #include "page.p"
  4290. #include "leadbl.p"
  4291. #include "spread.p"
  4292. #include "putword.p"
  4293. #include "getword.p"
  4294. #include "center.p"
  4295. #include "underln.p"
  4296. #include "initfmt.p"
  4297. #include "command.p"
  4298. #include "textz.p"
  4299.  
  4300. {!gettok.p!}
  4301.   { gettok -- get token for define }
  4302.   function GETTOK(var TOKEN : STRINGZ;
  4303.                   TOKSIZE : Integer) : CHARACTER;
  4304.   var
  4305.     I : Integer;
  4306.     DONE : Boolean;
  4307.   begin
  4308.     I := 1;
  4309.     DONE := False;
  4310.     while ((not DONE) and
  4311.            (I < TOKSIZE)) do
  4312.       if (ISALPHANUM(GETPBC(TOKEN[I]))) then
  4313.         I := I+1
  4314.       else
  4315.         DONE := True;
  4316.     if (I >= TOKSIZE) then
  4317.       ERROR('define: token too long');
  4318.     if (I > 1) then
  4319.       begin                     { some alpha was seen }
  4320.         PUTBACK(TOKEN[I]);
  4321.         I := I-1
  4322.       end;
  4323.     { else single non-alphanumeric }
  4324.     TOKEN[I+1] := ENDSTR;
  4325.     GETTOK := TOKEN[1]
  4326.   end;
  4327.  
  4328. {!putback.p!}
  4329.   { putback -- push character back onto input }
  4330.   procedure PUTBACK(C : CHARACTER);
  4331.   begin
  4332.     if (BP >= BUFSIZE) then
  4333.       ERROR('too many characters pushed back');
  4334.     BP := BP+1;
  4335.     BUF[BP] := C
  4336.   end;
  4337.  
  4338. {!getpbc.p!}
  4339.   { getpbc -- get a (possibly pushed back) character }
  4340.   function GETPBC(var C : CHARACTER) : CHARACTER;
  4341.   begin
  4342.     if (BP > 0) then
  4343.       C := BUF[BP]
  4344.     else
  4345.       begin
  4346.         BP := 1;
  4347.         BUF[BP] := GETC(C)
  4348.       end;
  4349.     if (C <> ENDFILE) then
  4350.       BP := BP-1;
  4351.     GETPBC := C
  4352.   end;
  4353.  
  4354. {!pbstr.p!}
  4355.   { pbstr -- push string back onto input }
  4356.   procedure PBSTR(var S : STRINGZ);
  4357.   var
  4358.     I : Integer;
  4359.   begin
  4360.     for I := LENGTHZ(S) downto 1 do
  4361.       PUTBACK(S[I])
  4362.   end;
  4363.  
  4364. {!define.p!}
  4365.   { define -- simple string replacement macro preprocessor }
  4366.   procedure DEFINE;
  4367.  
  4368. #include "defcons.p"
  4369. #include "deftype.p"
  4370. #include "defvar.p"
  4371.     DEFN : STRINGZ;
  4372.     TOKEN : STRINGZ;
  4373.     TOKTYPE : STTYPE;           { type returned by lookup }
  4374.     DEFNAME : STRINGZ;          { value is 'defined' }
  4375.     NULL : STRINGZ;             { value is '' }
  4376.  
  4377. #include "defproc.p"
  4378.   begin
  4379.     NULL[1] := ENDSTR;
  4380.     INITDEF;
  4381.     INSTALL(DEFNAME, NULL, DEFTYPE);
  4382.     while (GETTOK(TOKEN, MAXTOK) <> ENDFILE) do
  4383.       if (not ISLETTER(TOKEN[1])) then
  4384.         PUTSTR(TOKEN, STDOUT)
  4385.       else if (not LOOKUP(TOKEN, DEFN, TOKTYPE)) then
  4386.         PUTSTR(TOKEN, STDOUT)   { undefined }
  4387.       else if (TOKTYPE = DEFTYPE) then
  4388.         begin                   { defs }
  4389.           GETDEF(TOKEN, MAXTOK, DEFN, MAXDEF);
  4390.           INSTALL(TOKEN, DEFN, MACTYPE)
  4391.         end
  4392.       else
  4393.         PBSTR(DEFN)             { push replacement onto input }
  4394.   end;
  4395.  
  4396. {!isletter.p!}
  4397.   { isletter -- true if c is a letter of either case }
  4398.   function ISLETTER(C : CHARACTER) : Boolean;
  4399.   begin
  4400.     ISLETTER := C in [Ord('a') ..Ord('z')]+[Ord('A') ..Ord('Z')]
  4401.   end;
  4402.  
  4403. {!getdef.p!}
  4404.   { getdef -- get name and definiations }
  4405.   procedure GETDEF(var TOKEN : STRINGZ;
  4406.                    TOKSIZE : Integer;
  4407.                    var DEFN : STRINGZ;
  4408.                    DEFSIZE : Integer);
  4409.   var
  4410.     I, NLPAR : Integer;
  4411.     C : CHARACTER;
  4412.   begin
  4413.     TOKEN[1] := ENDSTR;         { in case of bad input }
  4414.     DEFN[1] := ENDSTR;
  4415.     if (GETPBC(C) <> LPAREN) then
  4416.       MESSAGE('define: missing left paren')
  4417.     else if (not ISLETTER(GETTOK(TOKEN, TOKSIZE))) then
  4418.       MESSAGE('define : non-alphanumeric name')
  4419.     else if (GETPBC(C) <> COMMA) then
  4420.       MESSAGE('define: missing comma in define')
  4421.     else
  4422.       begin                     { got '(name,'' so far }
  4423.         while (GETPBC(C) = BLANK) do ; { skip leading blanks }
  4424.         PUTBACK(C);             { went one too far }
  4425.         NLPAR := 0;
  4426.         I := 1;
  4427.         while (NLPAR >= 0) do
  4428.           begin
  4429.             if (I >= DEFSIZE) then
  4430.               ERROR('define: definition too long')
  4431.             else if (GETPBC(DEFN[I]) = ENDFILE) then
  4432.               ERROR('define: missing right paren')
  4433.             else if (DEFN[I] = LPAREN) then
  4434.               NLPAR := NLPAR+1
  4435.             else if (DEFN[I] = RPAREN) then
  4436.               NLPAR := NLPAR-1;
  4437.             { else normal character in defn[i] }
  4438.             I := I+1;
  4439.           end;
  4440.         DEFN[I-1] := ENDSTR
  4441.       end
  4442.   end;
  4443.  
  4444. {!initdef.p!}
  4445.   { initdef -- initialize variables for define }
  4446.   procedure INITDEF;
  4447.   begin
  4448.     { setstring(defname, 'define'); }
  4449.     DEFNAME[1] := Ord('d');
  4450.     DEFNAME[2] := Ord('e');
  4451.     DEFNAME[3] := Ord('f');
  4452.     DEFNAME[4] := Ord('i');
  4453.     DEFNAME[5] := Ord('n');
  4454.     DEFNAME[6] := Ord('e');
  4455.     DEFNAME[7] := ENDSTR;
  4456.     BP := 0;                    { pushback buffer pointer }
  4457.     INITHASH;
  4458.   end;
  4459.  
  4460. {!deftype.p!}
  4461.   { deftype -- type definitions for define }
  4462. type
  4463.   CHARPOS = 1..MAXCHARS;
  4464.   CHARBUF = array[1..MAXCHARS] of CHARACTER;
  4465.   STTYPE = (DEFTYPE, MACTYPE);  { symbol table types }
  4466.   NDPTR = ^NDBLOCK;             { pointer to a name-defn block }
  4467.   NDBLOCK = record              { name-defn block }
  4468.               NAME : CHARPOS;
  4469.               DEFN : CHARPOS;
  4470.               KIND : STTYPE;
  4471.               NEXTPTR : NDPTR
  4472.             end;
  4473.  
  4474. {!defvar.p!}
  4475.   { defvar -- var declarations for define }
  4476.   var
  4477.     HASHTAB : array[1..HASHSIZE] of NDPTR;
  4478.     NDTABLE : CHARBUF;
  4479.     NEXTTAB : CHARPOS;          { first free position in ndtable }
  4480.     BUF : array[1..BUFSIZE] of CHARACTER; { for pushback }
  4481.     BP : 0..BUFSIZE;            { next available character; init=0 }
  4482.  
  4483. {!inithash.p!}
  4484.   { inithash -- initialize hash table to nil }
  4485.   procedure INITHASH;
  4486.   var
  4487.     I : 1..HASHSIZE;
  4488.   begin
  4489.     NEXTTAB := 1;               { first free slot in table }
  4490.     for I := 1 to HASHSIZE do
  4491.       HASHTAB[I] := nil
  4492.   end;
  4493.  
  4494. {!lookup.p!}
  4495.   { lookup -- locate name, get defn and type from table }
  4496.   function LOOKUP(var NAME, DEFN : STRINGZ;
  4497.                   var T : STTYPE) : Boolean;
  4498.   var
  4499.     P : NDPTR;
  4500.   begin
  4501.     P := HASHFIND(NAME);
  4502.     if (P = nil) then
  4503.       LOOKUP := False
  4504.     else
  4505.       begin
  4506.         LOOKUP := True;
  4507.         CSCOPY(NDTABLE, P^.DEFN, DEFN);
  4508.         T := P^.KIND
  4509.       end
  4510.   end;
  4511.  
  4512. {!hashfind.p!}
  4513.   { hashfind -- find name in hash table }
  4514.   function HASHFIND(var NAME : STRINGZ) : NDPTR;
  4515.   var
  4516.     P : NDPTR;
  4517.     TEMPNAME : STRINGZ;
  4518.     FOUND : Boolean;
  4519.   begin
  4520.     FOUND := False;
  4521.     P := HASHTAB[HASH(NAME)];
  4522.     while ((not FOUND) and
  4523.            (P <> nil)) do
  4524.       begin
  4525.         CSCOPY(NDTABLE, P^.NAME, TEMPNAME);
  4526.         if (EQUAL(NAME, TEMPNAME)) then
  4527.           FOUND := True
  4528.         else
  4529.           P := P^.NEXTPTR
  4530.       end;
  4531.     HASHFIND := P
  4532.   end;
  4533.  
  4534. {!hash.p!}
  4535.   { hash -- compute hash function of a name }
  4536.   function HASH(var NAME : STRINGZ) : Integer;
  4537.   var
  4538.     I, H : Integer;
  4539.   begin
  4540.     H := 0;
  4541.     for I := 1 to LENGTHZ(NAME) do
  4542.       H := (3*H+NAME[I]) mod HASHSIZE;
  4543.     HASH := H+1
  4544.   end;
  4545.  
  4546. {!install.p!}
  4547.   { install -- add name, definition and type to table }
  4548.   procedure INSTALL(var NAME, DEFN : STRINGZ;
  4549.                     T : STTYPE);
  4550.   var
  4551.     H, DLEN, NLEN : Integer;
  4552.     P : NDPTR;
  4553.   begin
  4554.     NLEN := LENGTHZ(NAME)+1;    { 1 for ENDSTR }
  4555.     DLEN := LENGTHZ(DEFN)+1;
  4556.     if (NEXTTAB+NLEN+DLEN > MAXCHARS) then
  4557.       begin
  4558.         PUTSTR(NAME, STDERR);
  4559.         ERROR(': too many definitions')
  4560.       end
  4561.     else
  4562.       begin                     { put it at front of chain }
  4563.         H := HASH(NAME);
  4564.         New(P);
  4565.         P^.NEXTPTR := HASHTAB[H];
  4566.         HASHTAB[H] := P;
  4567.         P^.NAME := NEXTTAB;
  4568.         SCCOPY(NAME, NDTABLE, NEXTTAB);
  4569.         NEXTTAB := NEXTTAB+NLEN;
  4570.         P^.DEFN := NEXTTAB;
  4571.         SCCOPY(DEFN, NDTABLE, NEXTTAB);
  4572.         NEXTTAB := NEXTTAB+DLEN;
  4573.         P^.KIND := T
  4574.       end
  4575.   end;
  4576.  
  4577. {!defcons.p!}
  4578.   { defcons -- const declarations for define }
  4579.   const
  4580.   BUFSIZE = 500;                { size of pushback buffer }
  4581.   MAXCHARS = 5000;              { size of name-defn table }
  4582.   MAXDEF = MAXSTR;              { max chars in a defn }
  4583.   MAXTOK = MAXSTR;              { max chars in a token }
  4584.   HASHSIZE = 53;                { size of hash table }
  4585.  
  4586. {!defproc.p!}
  4587.   { defproc -- procedures needed by define }
  4588.  
  4589. #include "cscopy.p"
  4590. #include "sccopy.p"
  4591. #include "putback.p"
  4592. #include "getpbc.p"
  4593. #include "pbstr.p"
  4594. #include "gettok.p"
  4595. #include "getdef.p"
  4596. #include "inithash.p"
  4597. #include "hash.p"
  4598. #include "hashfind.p"
  4599. #include "install.p"
  4600. #include "lookup.p"
  4601. #include "initdef.p"
  4602.  
  4603. {!macro.p!}
  4604.   { macro -- expand macros with arguments }
  4605.   procedure MACRO;
  4606.  
  4607. #include "maccons.p"
  4608. #include "mactype.p"
  4609. #include "macvar.p"
  4610.     DEFN : STRINGZ;
  4611.     TOKEN : STRINGZ;
  4612.     TOKTYPE : STTYPE;
  4613.     T : CHARACTER;
  4614.     NLPAR : Integer;
  4615.  
  4616. #include "macproc.p"
  4617.   begin
  4618.     INITMACRO;
  4619.     INSTALL(DEFNAME, NULL, DEFTYPE);
  4620.     INSTALL(EXPRNAME, NULL, EXPRTYPE);
  4621.     INSTALL(SUBNAME, NULL, SUBTYPE);
  4622.     INSTALL(IFNAME, NULL, IFTYPE);
  4623.     INSTALL(LENNAME, NULL, LENTYPE);
  4624.     INSTALL(CHQNAME, NULL, CHQTYPE);
  4625.  
  4626.     CP := 0;
  4627.     AP := 1;
  4628.     EP := 1;
  4629.     while (GETTOK(TOKEN, MAXTOK) <> ENDFILE) do
  4630.       if (ISLETTER(TOKEN[1])) then
  4631.         begin
  4632.           if (not LOOKUP(TOKEN, DEFN, TOKTYPE)) then
  4633.             PUTTOK(TOKEN)
  4634.           else
  4635.             begin               { defined; put it in eval stack }
  4636.               CP := CP+1;
  4637.               if (CP > CALLSIZE) then
  4638.                 ERROR('macro: call stack overflow');
  4639.               CALLSTK[CP] := AP;
  4640.               TYPESTK[CP] := TOKTYPE;
  4641.               AP := PUSH(EP, ARGSTK, AP);
  4642.               PUTTOK(DEFN);     { push definition }
  4643.               PUTCHR(ENDSTR);
  4644.               AP := PUSH(EP, ARGSTK, AP);
  4645.               PUTTOK(TOKEN);    { stack name }
  4646.               PUTCHR(ENDSTR);
  4647.               AP := PUSH(EP,ARGSTK,AP);
  4648.               T := GETTOK(TOKEN,MAXTOK); { peek at next }
  4649.               PBSTR(TOKEN);
  4650.               if (T <> LPAREN) then
  4651.                 begin           { add () }
  4652.                   PUTBACK(RPAREN);
  4653.                   PUTBACK(LPAREN)
  4654.                 end;
  4655.               PLEV[CP] := 0
  4656.             end
  4657.         end
  4658.       else if (TOKEN[1] = LQUOTE) then
  4659.         begin                   { strip quotes }
  4660.           NLPAR := 1;
  4661.           repeat
  4662.             T := GETTOK(TOKEN, MAXTOK);
  4663.             if (T = RQUOTE) then
  4664.               NLPAR := NLPAR-1
  4665.             else if (T = LQUOTE) then
  4666.               NLPAR := NLPAR+1
  4667.             else if (T = ENDFILE) then
  4668.               ERROR('macro: missing right quote');
  4669.             if (NLPAR > 0) then
  4670.               PUTTOK(TOKEN)
  4671.           until (NLPAR = 0)
  4672.         end
  4673.       else if (CP = 0) then     { not in a macro at all }
  4674.         PUTTOK(TOKEN)
  4675.       else if (TOKEN[1] = LPAREN) then
  4676.         begin
  4677.           if (PLEV[CP] > 0) then
  4678.             PUTTOK(TOKEN);
  4679.           PLEV[CP] := PLEV[CP]+1
  4680.         end
  4681.       else if (TOKEN[1] = RPAREN) then
  4682.         begin
  4683.           PLEV[CP] := PLEV[CP]-1;
  4684.           if (PLEV[CP] > 0) then
  4685.             PUTTOK(TOKEN)
  4686.           else
  4687.             begin               { end of argument list }
  4688.               PUTCHR(ENDSTR);
  4689.               EVAL(ARGSTK, TYPESTK[CP], CALLSTK[CP], AP-1);
  4690.               AP := CALLSTK[CP]; { pop eval stack }
  4691.               EP := ARGSTK[AP];
  4692.               CP := CP-1
  4693.             end
  4694.         end
  4695.       else if ((TOKEN[1] = COMMA) and
  4696.                (PLEV[CP] = 1)) then
  4697.         begin
  4698.           PUTCHR(ENDSTR);       { new argument }
  4699.           AP := PUSH(EP, ARGSTK, AP)
  4700.         end
  4701.       else
  4702.         PUTTOK(TOKEN);          { just stack it }
  4703.     if (CP <> 0) then
  4704.       ERROR('macro: unexpected end of input')
  4705.   end;
  4706.  
  4707. {!puttok.p!}
  4708.   { puttok -- put token on output or evaluation stack }
  4709.   procedure PUTTOK(var S : STRINGZ);
  4710.   var
  4711.     I : Integer;
  4712.   begin
  4713.     I := 1;
  4714.     while (S[I] <> ENDSTR) do
  4715.       begin
  4716.         PUTCHR(S[I]);
  4717.         I := I+1
  4718.       end
  4719.   end;
  4720.  
  4721. {!putchr.p!}
  4722.   { putchr -- put single char on output or evaluation stack }
  4723.   procedure PUTCHR(C : CHARACTER);
  4724.   begin
  4725.     if (CP <= 0) then
  4726.       PUTC(C)
  4727.     else
  4728.       begin
  4729.         if (EP > EVALSIZE) then
  4730.           ERROR('macro: evaluation stack overflow');
  4731.         EVALSTK[EP] := C;
  4732.         EP := EP+1
  4733.       end
  4734.   end;
  4735.  
  4736. {!push.p!}
  4737.   { push -- push ep onto argstk, return new position ap }
  4738.   function PUSH(EP : Integer;
  4739.                 var ARGSTK : POSBUF;
  4740.                 AP : Integer) : Integer;
  4741.   begin
  4742.     if (AP > ARGSIZE) then
  4743.       ERROR('macro: argument stack overflow');
  4744.     ARGSTK[AP] := EP;
  4745.     PUSH := AP+1
  4746.   end;
  4747.  
  4748. {!eval.p!}
  4749.   { eval -- expand args i..j: do built-in or push back defn }
  4750.   procedure EVAL(var ARGSTK : POSBUF;
  4751.                  TD : STTYPE;
  4752.                  I, J : Integer);
  4753.   var
  4754.     ARGNO, K, T : Integer;
  4755.     TEMP : STRINGZ;
  4756.   begin
  4757.     T := ARGSTK[I];
  4758.     if (TD = DEFTYPE) then
  4759.       DODEF(ARGSTK, I, J)
  4760.     else if (TD = EXPRTYPE) then
  4761.       DOEXPR(ARGSTK, I, J)
  4762.     else if (TD = SUBTYPE) then
  4763.       DOSUB(ARGSTK, I, J)
  4764.     else if (TD = IFTYPE) then
  4765.       DOIF(ARGSTK, I, J)
  4766.     else if (TD = LENTYPE) then
  4767.       DOLEN(ARGSTK, I, J)
  4768.     else if (TD = CHQTYPE) then
  4769.       DOCHQ(ARGSTK, I, J)
  4770.     else
  4771.       begin
  4772.         K := T;
  4773.         while (EVALSTK[K] <> ENDSTR) do
  4774.           K := K+1;
  4775.         K := K-1;               { last character of defn }
  4776.         while (K > T) do
  4777.           begin
  4778.             if (EVALSTK[K-1] <> ARGFLAG) then
  4779.               PUTBACK(EVALSTK[K])
  4780.             else
  4781.               begin
  4782.                 ARGNO := Ord(EVALSTK[K])-Ord('0');
  4783.                 if ((ARGNO >= 0) and
  4784.                     (ARGNO < J-I)) then
  4785.                   begin
  4786.                     CSCOPY(EVALSTK, ARGSTK[I+ARGNO+1], TEMP);
  4787.                     PBSTR(TEMP)
  4788.                   end;
  4789.                 K := K-1        { skip over $ }
  4790.               end;
  4791.             K := K-1
  4792.           end;
  4793.         if (K = T) then         { do last character }
  4794.           PUTBACK(EVALSTK[K])
  4795.       end
  4796.   end;
  4797.  
  4798. {!dodef.p!}
  4799.   { dodef -- install definitions in table }
  4800.   procedure DODEF(var ARGSTK : POSBUF;
  4801.                   I, J : Integer);
  4802.   var
  4803.     TEMP1, TEMP2 : STRINGZ;
  4804.   begin
  4805.     if (J-I > 2) then
  4806.       begin
  4807.         CSCOPY(EVALSTK, ARGSTK[I+2], TEMP1);
  4808.         CSCOPY(EVALSTK, ARGSTK[I+3], TEMP2);
  4809.         INSTALL(TEMP1, TEMP2, MACTYPE)
  4810.       end
  4811.   end;
  4812.  
  4813. {!doif.p!}
  4814.   { doif -- select one of two arguments }
  4815.   procedure DOIF(var ARGSTK : POSBUF;
  4816.                  I, J : Integer);
  4817.   var
  4818.     TEMP1, TEMP2, TEMP3 : STRINGZ;
  4819.   begin
  4820.     if (J-I >= 4) then
  4821.       begin
  4822.         CSCOPY(EVALSTK, ARGSTK[I+2], TEMP1);
  4823.         CSCOPY(EVALSTK, ARGSTK[I+3], TEMP2);
  4824.         if (EQUAL(TEMP1, TEMP2)) then
  4825.           CSCOPY(EVALSTK, ARGSTK[I+4], TEMP3)
  4826.         else if (J-I >= 5) then
  4827.           CSCOPY(EVALSTK, ARGSTK[I+5], TEMP3)
  4828.         else
  4829.           TEMP3[1] := ENDSTR;
  4830.         PBSTR(TEMP3)
  4831.       end
  4832.   end;
  4833.  
  4834. {!doexpr.p!}
  4835.   { doexpr -- evaluate arithmetic expressions }
  4836.   procedure DOEXPR(var ARGSTK : POSBUF;
  4837.                    I, J : Integer);
  4838.   var
  4839.     TEMP : STRINGZ;
  4840.     JUNK : Integer;
  4841.   begin
  4842.     CSCOPY(EVALSTK, ARGSTK[I+2], TEMP);
  4843.     JUNK := 1;
  4844.     PBNUM(EXPR(TEMP, JUNK))
  4845.   end;
  4846.  
  4847. {!pbnum.p!}
  4848.   { pbnum -- convert number to string, push back on input }
  4849.   procedure PBNUM(N : Integer);
  4850.   var
  4851.     TEMP : STRINGZ;
  4852.     JUNK : Integer;
  4853.   begin
  4854.     JUNK := ITOC(N, TEMP, 1);
  4855.     PBSTR(TEMP)
  4856.   end;
  4857.  
  4858. {!expr.p!}
  4859.   { expr -- recursive expression evaluation }
  4860.   function EXPR(var S : STRINGZ;
  4861.                 var I : Integer) : Integer;
  4862.   var
  4863.     V : Integer;
  4864.     T : CHARACTER;
  4865.  
  4866. #include "gnbchar.p"
  4867. #include "term.p"
  4868.   begin
  4869.     V := TERM(S, I);
  4870.     T := GNBCHAR(S, I);
  4871.     while (T in [PLUS, MINUS]) do
  4872.       begin
  4873.         I := I+1;
  4874.         if (T = PLUS) then
  4875.           V := V+TERM(S, I)
  4876.         else
  4877.           V := V-TERM(S, I);
  4878.         T := GNBCHAR(S, I)
  4879.       end;
  4880.     EXPR := V
  4881.   end;
  4882.  
  4883. {!term.p!}
  4884.   { term -- evaluate term of arithmetic expression }
  4885.   function TERM(var S : STRINGZ;
  4886.                 var I : Integer) : Integer;
  4887.   var
  4888.     V : Integer;
  4889.     T : CHARACTER;
  4890.  
  4891. #include "factor.p"
  4892.   begin
  4893.     V := FACTOR(S, I);
  4894.     T := GNBCHAR(S, I);
  4895.     while (T in [STAR, SLASH, PERCENT]) do
  4896.       begin
  4897.         I := I+1;
  4898.         case T of
  4899.           STAR :
  4900.             V := V*FACTOR(S, I);
  4901.           SLASH :
  4902.             V := V div FACTOR(S, I);
  4903.           PERCENT :
  4904.             V := V mod FACTOR(S, I)
  4905.         end;
  4906.         T := GNBCHAR(S, I)
  4907.       end;
  4908.     TERM := V
  4909.   end;
  4910.  
  4911. {!factor.p!}
  4912.   { factor -- evaluate factor of arithmetic expression }
  4913.   function FACTOR(var S : STRINGZ;
  4914.                   var I : Integer) : Integer;
  4915.   begin
  4916.     if (GNBCHAR(S, I) = LPAREN) then
  4917.       begin
  4918.         I := I+1;
  4919.         FACTOR := EXPR(S, I);
  4920.         if (GNBCHAR(S, I) = RPAREN) then
  4921.           I := I+1
  4922.         else
  4923.           WriteLn('macro: missing paren in expr')
  4924.       end
  4925.     else
  4926.       FACTOR := CTOI(S, I)
  4927.   end;
  4928.  
  4929. {!gnbchar.p!}
  4930.   { gnbchar -- get next non-blank character }
  4931.   function GNBCHAR(var S : STRINGZ;
  4932.                    var I : Integer) : CHARACTER;
  4933.   begin
  4934.     while (S[I] in [BLANK, TAB, NEWLINE]) do
  4935.       I := I+1;
  4936.     GNBCHAR := S[I]
  4937.   end;
  4938.  
  4939. {!dolen.p!}
  4940.   { dolen -- return length of argument }
  4941.   procedure DOLEN(var ARGSTK : POSBUF;
  4942.                   I, J : Integer);
  4943.   var
  4944.     TEMP : STRINGZ;
  4945.   begin
  4946.     if (J-I > 1) then
  4947.       begin
  4948.         CSCOPY(EVALSTK, ARGSTK[I+2], TEMP);
  4949.         PBNUM(LENGTHZ(TEMP))
  4950.       end
  4951.     else
  4952.       PBNUM(0)
  4953.   end;
  4954.  
  4955. {!dosub.p!}
  4956.   { dosub -- select substring }
  4957.   procedure DOSUB(var ARGSTK : POSBUF;
  4958.                   I, J : Integer);
  4959.   var
  4960.     AP, FC, K, NC : Integer;
  4961.     TEMP1, TEMP2 : STRINGZ;
  4962.   begin
  4963.     if (J-I >= 3) then
  4964.       begin
  4965.         if (J-I < 4) then
  4966.           NC := MAXTOK
  4967.         else
  4968.           begin
  4969.             CSCOPY(EVALSTK, ARGSTK[I+4], TEMP1);
  4970.             K := 1;
  4971.             NC := EXPR(TEMP1, K)
  4972.           end;
  4973.         CSCOPY(EVALSTK, ARGSTK[I+3], TEMP1); { origin }
  4974.         AP := ARGSTK[I+2];      { target string }
  4975.         K := 1;
  4976.         FC := AP+EXPR(TEMP1, K)-1; { first char }
  4977.         CSCOPY(EVALSTK, AP, TEMP2);
  4978.         if ((FC >= AP) and
  4979.             (FC < AP+LENGTHZ(TEMP2))) then
  4980.           begin
  4981.             CSCOPY(EVALSTK, FC, TEMP1);
  4982.             for K := FC+MIN(NC, LENGTHZ(TEMP1))-1 downto FC do
  4983.               PUTBACK(EVALSTK[K])
  4984.           end
  4985.       end
  4986.   end;
  4987.  
  4988. {!dochq.p!}
  4989.   { dochq -- change quote characters }
  4990.   procedure DOCHQ(var ARGSTK : POSBUF;
  4991.                   I, J : Integer);
  4992.   var
  4993.     TEMP : STRINGZ;
  4994.     N : Integer;
  4995.   begin
  4996.     CSCOPY(EVALSTK, ARGSTK[I+2], TEMP);
  4997.     N := LENGTHZ(TEMP);
  4998.     if (N <= 0) then
  4999.       begin
  5000.         LQUOTE := Ord(GRAVE);
  5001.         RQUOTE := Ord(ACUTE)
  5002.       end
  5003.     else if (N = 1) then
  5004.       begin
  5005.         LQUOTE := TEMP[1];
  5006.         RQUOTE := LQUOTE
  5007.       end
  5008.     else
  5009.       begin
  5010.         LQUOTE := TEMP[1];
  5011.         RQUOTE := TEMP[2]
  5012.       end
  5013.   end;
  5014.  
  5015. {!mactype.p!}
  5016.   { mactype -- type declarations for macro }
  5017. type
  5018.   CHARPOS = 1..MAXCHARS;
  5019.   CHARBUF = array[1..MAXCHARS] of CHARACTER;
  5020.   POSBUF = array[1..MAXPOS] of CHARPOS;
  5021.   POSZ = 0..MAXPOS;
  5022.   STTYPE = (DEFTYPE, MACTYPE, IFTYPE, SUBTYPE,
  5023.             EXPRTYPE, LENTYPE, CHQTYPE); { symbol table types }
  5024.   NDPTR = ^NDBLOCK;
  5025.   NDBLOCK = record
  5026.               NAME : CHARPOS;
  5027.               DEFN : CHARPOS;
  5028.               KIND : STTYPE;
  5029.               NEXTPTR : NDPTR
  5030.             end;
  5031.  
  5032. {!maccons.p!}
  5033.   { maccons -- const declarations for macro }
  5034.   const
  5035.   BUFSIZE = 1000;               { size of pushback buffer }
  5036.   MAXCHARS = 5000;              { size of name-defn table }
  5037.   MAXPOS = 500;                 { size of position arrays }
  5038.   CALLSIZE = MAXPOS;
  5039.   ARGSIZE = MAXPOS;
  5040.   EVALSIZE = MAXCHARS;
  5041.   MAXDEF = MAXSTR;              { max chars in a defn }
  5042.   MAXTOK = MAXSTR;              { max chars in a token }
  5043.   HASHSIZE = 53;                { size of hash table }
  5044.   ARGFLAG = DOLLAR;             { macro invocation character }
  5045.  
  5046. {!macvar.p!}
  5047.   { macvar -- var declarations for macro }
  5048.   var
  5049.     BUF : array[1..BUFSIZE] of CHARACTER; { for pushback }
  5050.     BP : 0..BUFSIZE;            { next available character; init=0 }
  5051.  
  5052.     HASHTAB : array[1..HASHSIZE] of NDPTR;
  5053.     NDTABLE : CHARBUF;
  5054.     NEXTTAB : CHARPOS;          { first free position in ndtable }
  5055.  
  5056.     CALLSTK : POSBUF;           { call stack }
  5057.     CP : POSZ;                  { current call stack position }
  5058.     TYPESTK : array[1..CALLSIZE] of STTYPE; { type }
  5059.     PLEV : array[1..CALLSIZE] of Integer; { paren level }
  5060.     ARGSTK : POSBUF;            { argument stack for this call }
  5061.     AP : POSZ;                  { current argument position }
  5062.     EVALSTK : CHARBUF;          { evaluation stack }
  5063.     EP : CHARPOS;               { first character unused in evalstk }
  5064.  
  5065.     { built-ins: }
  5066.     DEFNAME : STRINGZ;          { value is 'define' }
  5067.     EXPRNAME : STRINGZ;         { value is 'expr' }
  5068.     SUBNAME : STRINGZ;          { value is 'substr' }
  5069.     IFNAME : STRINGZ;           { value is 'ifelse' }
  5070.     LENNAME : STRINGZ;          { value is 'len' }
  5071.     CHQNAME : STRINGZ;          { vlaue is 'changeq' }
  5072.  
  5073.     NULL : STRINGZ;             { value is '' }
  5074.     LQUOTE : CHARACTER;         { left quote character }
  5075.     RQUOTE : CHARACTER;         { right quote character }
  5076.  
  5077. {!initmacr.p!}
  5078.   { initmacro -- initialize variables for macro }
  5079.   procedure INITMACRO;
  5080.   begin
  5081.     NULL[1] := ENDSTR;
  5082.     { setstring(defname, 'define'); }
  5083.     DEFNAME[1] := Ord('d');
  5084.     DEFNAME[2] := Ord('e');
  5085.     DEFNAME[3] := Ord('f');
  5086.     DEFNAME[4] := Ord('i');
  5087.     DEFNAME[5] := Ord('n');
  5088.     DEFNAME[6] := Ord('e');
  5089.     DEFNAME[7] := ENDSTR;
  5090.     { setstring(subname, 'substr'); }
  5091.     SUBNAME[1] := Ord('s');
  5092.     SUBNAME[2] := Ord('u');
  5093.     SUBNAME[3] := Ord('b');
  5094.     SUBNAME[4] := Ord('s');
  5095.     SUBNAME[5] := Ord('t');
  5096.     SUBNAME[6] := Ord('r');
  5097.     SUBNAME[7] := ENDSTR;
  5098.     { setstring(exprname, 'expr'); }
  5099.     EXPRNAME[1] := Ord('e');
  5100.     EXPRNAME[2] := Ord('x');
  5101.     EXPRNAME[3] := Ord('p');
  5102.     EXPRNAME[4] := Ord('r');
  5103.     EXPRNAME[5] := ENDSTR;
  5104.     { setstring(ifname, 'ifelse'); }
  5105.     IFNAME[1] := Ord('i');
  5106.     IFNAME[2] := Ord('f');
  5107.     IFNAME[3] := Ord('e');
  5108.     IFNAME[4] := Ord('l');
  5109.     IFNAME[5] := Ord('s');
  5110.     IFNAME[6] := Ord('e');
  5111.     IFNAME[7] := ENDSTR;
  5112.     { setstring(lenname, 'len'); }
  5113.     LENNAME[1] := Ord('l');
  5114.     LENNAME[2] := Ord('e');
  5115.     LENNAME[3] := Ord('n');
  5116.     LENNAME[4] := ENDSTR;
  5117.     { setstring(chqname, 'changeq'); }
  5118.     CHQNAME[1] := Ord('c');
  5119.     CHQNAME[2] := Ord('h');
  5120.     CHQNAME[3] := Ord('a');
  5121.     CHQNAME[4] := Ord('n');
  5122.     CHQNAME[5] := Ord('g');
  5123.     CHQNAME[6] := Ord('e');
  5124.     CHQNAME[7] := Ord('q');
  5125.     CHQNAME[8] := ENDSTR;
  5126.     BP := 0;                    { pushback buffer pointer }
  5127.     INITHASH;
  5128.     LQUOTE := Ord(GRAVE);
  5129.     RQUOTE := Ord(ACUTE)
  5130.   end;
  5131.  
  5132. {!macproc.p!}
  5133. { macproc -- procedures for macro program }
  5134.  
  5135. #include "inithash.p"
  5136. #include "initmacr.p"
  5137. #include "hash.p"
  5138. #include "sccopy.p"
  5139. #include "install.p"
  5140. #include "getpbc.p"
  5141. #include "putback.p"
  5142. #include "gettok.p"
  5143. #include "cscopy.p"
  5144. #include "hashfind.p"
  5145. #include "lookup.p"
  5146. #include "putchr.p"
  5147. #include "puttok.p"
  5148. #include "push.p"
  5149. #include "pbstr.p"
  5150. #include "dodef.p"
  5151. #include "pbnum.p"
  5152. #include "expr.p"
  5153. #include "doexpr.p"
  5154. #include "dosub.p"
  5155. #include "doif.p"
  5156. #include "dolen.p"
  5157. #include "dochq.p"
  5158. #include "eval.p"
  5159.  
  5160. {!getc.p!}
  5161.   { getc -- (TP7) get one character from standard input }
  5162.   function GETC(var C : CHARACTER) : CHARACTER;
  5163.   var
  5164.     CH : Char;
  5165.   begin
  5166.     if (Eof) then
  5167.       C := ENDFILE
  5168.     else if (Eoln) then
  5169.       begin
  5170.         ReadLn;
  5171.         C := NEWLINE;
  5172.       end
  5173.     else
  5174.       begin
  5175.         Read(CH);
  5176.         C := Ord(CH);
  5177.       end;
  5178.     GETC := C;
  5179.   end;
  5180.  
  5181. {!putc.p!}
  5182.   { putc -- (TP7) put one character on standard output }
  5183.   procedure PUTC(C : CHARACTER);
  5184.   begin
  5185.     if (C = NEWLINE) then
  5186.       WriteLn
  5187.     else
  5188.       Write(Chr(C));
  5189.   end;
  5190.  
  5191. {!prims.p!}
  5192.   { prims -- (TP7) primitive functions and procedures }
  5193.  
  5194. #include "initio.p"
  5195. #include "open.p"
  5196. #include "create.p"
  5197. #include "getc.p"
  5198. #include "getcf.p"
  5199. #include "getline.p"
  5200. #include "putc.p"
  5201. #include "putcf.p"
  5202. #include "putstr.p"
  5203. #include "closez.p"
  5204. #include "remove.p"
  5205. #include "getarg.p"
  5206. #include "nargs.p"
  5207.  
  5208. {!utility.p!}
  5209.   { utility -- generally useful function and procedures }
  5210.  
  5211. #include "addstr.p"
  5212. #include "equal.p"
  5213. #include "esc.p"
  5214. #include "indexz.p"
  5215. #include "isalnum.p"
  5216. #include "isdigit.p"
  5217. #include "isletter.p"
  5218. #include "islower.p"
  5219. #include "isupper.p"
  5220. #include "itoc.p"
  5221. #include "lengthz.p"
  5222. #include "max.p"
  5223. #include "min.p"
  5224. #include "scopy.p"
  5225. #include "ctoi.p"
  5226. #include "fcopy.p"
  5227. #include "mustcrea.p"
  5228. #include "mustopen.p"
  5229. #include "putdec.p"
  5230.  
  5231. {!islower.p!}
  5232.   { islower -- true if c is lower case letter }
  5233.   function ISLOWER(C : CHARACTER) : Boolean;
  5234.   begin
  5235.     ISLOWER := C in [Ord('a') ..Ord('z')]
  5236.   end;
  5237.  
  5238. {!globdefs.p!}
  5239.   { globdefs -- global constants, types and variables }
  5240.  
  5241. const
  5242.  
  5243.   { standard file descriptors, subscripts in open, etc. }
  5244.   STDIN = 1;                    { these are not to be changed }
  5245.   STDOUT = 2;
  5246.   STDERR = 3;
  5247.  
  5248.   { other io-related stuff }
  5249.   IOERROR = 0;                  { status values for open files }
  5250.   IOAVAIL = 1;
  5251.   IOREAD = 2;
  5252.   IOWRITE = 3;
  5253.   MAXOPEN = 10;                 { maximum number of open files }
  5254.  
  5255.   { universal manifest constants }
  5256.   ENDFILE = 255;
  5257.   ENDSTR = 0;                   { null-terminated stringzs }
  5258.   MAXSTR = 100;                 { longest possible stringz }
  5259.  
  5260.   { ascii character set in decimal }
  5261.   BACKSPACE = 8;
  5262.   TAB = 9;
  5263.   NEWLINE = 10;                 { line feed }
  5264.   BLANK = Ord(' ');
  5265.   EXCLAM = Ord('!');
  5266.   DQUOTE = Ord('"');
  5267.   SHARP = Ord('#');
  5268.   DOLLAR = Ord('$');
  5269.   PERCENT = Ord('%');
  5270.   AMPER = Ord('&');
  5271.   SQUOTE = Ord('''');
  5272.   ACUTE = SQUOTE;
  5273.   LPAREN = Ord('(');
  5274.   RPAREN = Ord(')');
  5275.   STAR = Ord('*');
  5276.   PLUS = Ord('+');
  5277.   COMMA = Ord(',');
  5278.   MINUS = Ord('-');
  5279.   DASH = MINUS;
  5280.   PERIOD = Ord('.');
  5281.   SLASH = Ord('/');
  5282.   COLON = Ord(':');
  5283.   SEMICOL = Ord(';');
  5284.   LESS = Ord('<');
  5285.   EQUALS = Ord('=');
  5286.   GREATER = Ord('>');
  5287.   QUESTION = Ord('?');
  5288.   ATSIGN = Ord('@');
  5289.   LBRACK = Ord('[');
  5290.   BACKSLASH = Ord('\');
  5291.   RBRACK = Ord(']');
  5292.   CARET = Ord('^');
  5293.   UNDERLINE = Ord('_');
  5294.   GRAVE = Ord('`');
  5295.   LBRACE = Ord('{');
  5296.   BAR = Ord('|');
  5297.   RBRACE = Ord('}');
  5298.   TILDE = Ord('~');
  5299.  
  5300. type
  5301.   CHARACTER = Byte;             { byte-sized. ascii + other stuff }
  5302.   STRINGZ = array[1..MAXSTR] of CHARACTER;
  5303.   FILEDESC = IOERROR..MAXOPEN;
  5304.   IOBLOCK = record              { to keep track of open files }
  5305.               FILEVAR : Text;
  5306.               MODE : IOERROR..IOWRITE;
  5307.             end;
  5308.  
  5309. var
  5310.   OPENLIST : array[1..MAXOPEN] of IOBLOCK; { open files }
  5311.  
  5312. {!initio.p!}
  5313.   { initio -- (TP7) initialize open file list }
  5314.   procedure INITIO;
  5315.   var
  5316.     INDEX : Integer;
  5317.   begin
  5318.     OPENLIST[STDIN].MODE := IOREAD;
  5319.     OPENLIST[STDOUT].MODE := IOWRITE;
  5320.     OPENLIST[STDERR].MODE := IOWRITE;
  5321.  
  5322.     { connect STDERR to user's terminal ... }
  5323.     Assign(OPENLIST[STDERR].FILEVAR, '');
  5324.     Rewrite(OPENLIST[STDERR].FILEVAR);
  5325.  
  5326.     for INDEX := STDERR+1 to MAXOPEN do
  5327.       OPENLIST[INDEX].MODE := IOAVAIL;
  5328.   end;
  5329.  
  5330. {!open.p!}
  5331.   { open -- (TP7) make a file available for input or output }
  5332.   function OPEN(NAME : STRINGZ;
  5333.                 MODE : Integer) : FILEDESC;
  5334.   var
  5335.     FILE_NAME : String[MAXSTR];
  5336.     INDEX : Integer;
  5337.     FOUND : Boolean;
  5338.   begin
  5339.     OPEN := IOERROR;
  5340.     if ((MODE = IOREAD) or
  5341.         (MODE = IOWRITE)) then
  5342.       begin
  5343.         INDEX := 1;
  5344.         while (NAME[INDEX] <> ENDSTR) do
  5345.           begin
  5346.             FILE_NAME[INDEX] := Chr(NAME[INDEX]);
  5347.             Inc(INDEX);
  5348.           end;
  5349.         FILE_NAME[0] := Chr(INDEX-1);
  5350.         { find a free slot in openlist }
  5351.         FOUND := False;
  5352.         INDEX := 1;
  5353.         while ((INDEX <= MAXOPEN) and
  5354.                (not FOUND)) do
  5355.           begin
  5356.             if (OPENLIST[INDEX].MODE = IOAVAIL) then
  5357.               begin
  5358.                 OPENLIST[INDEX].MODE := MODE;
  5359.                 Assign(OPENLIST[INDEX].FILEVAR, FILE_NAME);
  5360.                 {$I-}
  5361.                 if (MODE = IOREAD) then
  5362.                   Reset(OPENLIST[INDEX].FILEVAR)
  5363.                 else
  5364.                   Rewrite(OPENLIST[INDEX].FILEVAR);
  5365.                 {$I+}
  5366.                 if (IoResult = 0) then
  5367.                   OPEN := INDEX;
  5368.                 FOUND := True;
  5369.               end;
  5370.             Inc(INDEX);
  5371.           end;
  5372.       end;
  5373.   end;
  5374.  
  5375. {!create.p!}
  5376.   { create -- (TP7) make a new instance of a file available }
  5377.   function CREATE(NAME : STRINGZ;
  5378.                   MODE : Integer) : FILEDESC;
  5379.   var
  5380.     FILE_NAME : String[MAXSTR];
  5381.     INDEX : Integer;
  5382.     FOUND : Boolean;
  5383.   begin
  5384.     CREATE := IOERROR;
  5385.     if (MODE = IOWRITE) then
  5386.       begin
  5387.         INDEX := 1;
  5388.         while (NAME[INDEX] <> ENDSTR) do
  5389.           begin
  5390.             FILE_NAME[INDEX] := Chr(NAME[INDEX]);
  5391.             Inc(INDEX);
  5392.           end;
  5393.         FILE_NAME[0] := Chr(INDEX-1);
  5394.         { find a free slot in openlist }
  5395.         FOUND := False;
  5396.         INDEX := 1;
  5397.         while ((INDEX <= MAXOPEN) and
  5398.                (not FOUND)) do
  5399.           begin
  5400.             if (OPENLIST[INDEX].MODE = IOAVAIL) then
  5401.               begin
  5402.                 OPENLIST[INDEX].MODE := MODE;
  5403.                 Assign(OPENLIST[INDEX].FILEVAR, FILE_NAME);
  5404.                 {$I-}
  5405.                 Rewrite(OPENLIST[INDEX].FILEVAR);
  5406.                 {$I+}
  5407.                 if (IoResult = 0) then
  5408.                   CREATE := INDEX;
  5409.                 FOUND := True;
  5410.               end;
  5411.             Inc(INDEX);
  5412.           end;
  5413.       end;
  5414.   end;
  5415.  
  5416. {!closez.p!}
  5417.   { closez -- (TP7) close a file and release slot }
  5418.   procedure CLOSEZ(FD : FILEDESC);
  5419.   begin
  5420.     if ((FD > STDERR) and
  5421.         (FD <= MAXOPEN)) then
  5422.       begin
  5423.         Close(OPENLIST[FD].FILEVAR);
  5424.         OPENLIST[FD].MODE := IOAVAIL;
  5425.       end;
  5426.   end;
  5427.  
  5428. {!remove.p!}
  5429.   { remove -- (TP7) remove a file }
  5430.   procedure REMOVE(NAME : STRINGZ);
  5431.   var
  5432.     INDEX : Integer;
  5433.     FILE_NAME : String[MAXSTR];
  5434.     FILE_VAR : file;
  5435.   begin
  5436.     INDEX := 1;
  5437.     while (NAME[INDEX] <> ENDSTR) do
  5438.       begin
  5439.         FILE_NAME[INDEX] := Chr(NAME[INDEX]);
  5440.         Inc(INDEX);
  5441.       end;
  5442.     FILE_NAME[0] := Chr(INDEX-1);
  5443.     { make sure file exists before removing it }
  5444.     Assign(FILE_VAR, FILE_NAME);
  5445.     Rewrite(FILE_VAR);
  5446.     Close(FILE_VAR);
  5447.     Erase(FILE_VAR);
  5448.   end;
  5449.  
  5450. {!getline.p!}
  5451.   { getline -- (TP7) get one line of text from a file }
  5452.   function GETLINE(var S : STRINGZ;
  5453.                    FD : FILEDESC;
  5454.                    MAXSIZE : Integer) : Boolean;
  5455.   var
  5456.     I : Integer;
  5457.     C : CHARACTER;
  5458.   begin
  5459.     I := 1;
  5460.     repeat
  5461.       S[I] := GETCF(C, FD);
  5462.       I := I+1;
  5463.     until ((C = ENDFILE) or
  5464.            (C = NEWLINE) or
  5465.            (I >= MAXSIZE));
  5466.     if (C = ENDFILE) then       { went one too far }
  5467.       I := I-1;
  5468.     S[I] := ENDSTR;
  5469.     GETLINE := (C <> ENDFILE);
  5470.   end;
  5471.  
  5472. {!putstr.p!}
  5473.   { putstr -- (TP7) put stringz in a file }
  5474.   procedure PUTSTR(var S : STRINGZ;
  5475.                    FD : FILEDESC);
  5476.   var
  5477.     I : Integer;
  5478.   begin
  5479.     I := 1;
  5480.     while (S[I] <> ENDSTR) do
  5481.       begin
  5482.         PUTCF(S[I], FD);
  5483.         I := I+1;
  5484.       end;
  5485.   end;
  5486.  
  5487. {!getcf.p!}
  5488.   { getcf -- (TP7) get one character from a file }
  5489.   function GETCF(var C : CHARACTER;
  5490.                  FD : FILEDESC) : CHARACTER;
  5491.   var
  5492.     CH : Char;
  5493.   begin
  5494.     if (FD = STDIN) then
  5495.       GETCF := GETC(C)
  5496.     else if Eof(OPENLIST[FD].FILEVAR) then
  5497.       C := ENDFILE
  5498.     else if Eoln(OPENLIST[FD].FILEVAR) then
  5499.       begin
  5500.         ReadLn(OPENLIST[FD].FILEVAR);
  5501.         C := NEWLINE;
  5502.       end
  5503.     else
  5504.       begin
  5505.         Read(OPENLIST[FD].FILEVAR, CH);
  5506.         C := Ord(CH);
  5507.       end;
  5508.     GETCF := C;
  5509.   end;
  5510.  
  5511. {!putcf.p!}
  5512.   { putcf -- (TP7) put a single character in a file }
  5513.   procedure PUTCF(C : CHARACTER;
  5514.                   FD : FILEDESC);
  5515.   begin
  5516.     if (FD = STDOUT) then
  5517.       PUTC(C)
  5518.     else if (C = NEWLINE) then
  5519.       WriteLn(OPENLIST[FD].FILEVAR)
  5520.     else
  5521.       Write(OPENLIST[FD].FILEVAR, Chr(C));
  5522.   end;
  5523.  
  5524. {!nargs.p!}
  5525.   { nargs -- (TP7) return number of arguments }
  5526.   function NARGS : Integer;
  5527.   begin
  5528.     if (NARG = -1) then
  5529.       PARSE_ARG;
  5530.     NARGS := NARG;
  5531.   end;
  5532.  
  5533. {!getarg.p!}
  5534.   { getarg -- (TP7) get n-th command line argument into s }
  5535.   function GETARG(N : Integer;
  5536.                   var S : STRINGZ;
  5537.                   MAXSIZE : Integer) : Boolean;
  5538.   var
  5539.     START, FINISH : Integer;
  5540.     SOURCE, DESTINATION, LEN, COUNT : Integer;
  5541.   begin
  5542.     if (NARG = -1) then
  5543.       PARSE_ARG;
  5544.     if ((N < 1) or
  5545.         (NARG < N)) then
  5546.       GETARG := False
  5547.     else
  5548.       begin
  5549.         START := ARGS[N, 1];
  5550.         FINISH := ARGS[N, 2];
  5551.         LEN := FINISH-START+1;
  5552.         if (MAXSIZE < LEN) then
  5553.           LEN := MAXSIZE;
  5554.         if (LEN >= 1) then
  5555.           begin
  5556.             DESTINATION := 1;
  5557.             SOURCE := START;
  5558.             COUNT := LEN;
  5559.             repeat
  5560.               S[DESTINATION] := Ord(CMD_TAIL[SOURCE]);
  5561.               Inc(DESTINATION);
  5562.               Inc(SOURCE);
  5563.               Dec(COUNT);
  5564.             until (COUNT = 0);
  5565.           end;
  5566.         S[LEN+1] := ENDSTR;
  5567.         GETARG := True;
  5568.       end;
  5569.   end;
  5570.  
  5571. {!seekz.p!}
  5572.   { seekz -- position file access pointer }
  5573.   procedure SEEKZ(RECNO : Integer;
  5574.                   FD : FILEDESC);
  5575.   var
  5576.     JUNK : Boolean;
  5577.     TEMP : STRINGZ;
  5578.   begin
  5579.     if (RECNO < RECIN) then
  5580.       begin
  5581.         CLOSEZ(FD);
  5582.         { cheat: open scratch file by name }
  5583.         FD := MUSTOPEN(EDITTEMP, IOREAD);
  5584.         RECIN := 1;
  5585.       end;
  5586.     while (RECIN < RECNO) do
  5587.       begin
  5588.         JUNK := GETLINE(TEMP, FD, MAXSTR);
  5589.         RECIN := RECIN+1
  5590.       end
  5591.   end;
  5592.  
  5593. {!error.p!}
  5594.   { error -- (TP7) print a message and exit program }
  5595.   procedure ERROR(S : String);
  5596.   begin
  5597.     WriteLn;
  5598.     WriteLn(S);
  5599.     Halt(0);
  5600.   end;
  5601.  
  5602. {!message.p!}
  5603.   { message -- (TP7) print a message and continue }
  5604.   procedure MESSAGE(S : String);
  5605.   begin
  5606.     WriteLn(S);
  5607.   end;
  5608.  
  5609. {!globdefs.i!}
  5610. {$I COMPILER.INC}
  5611. #include "header.p"
  5612. unit GLOBDEFS;
  5613.  
  5614. interface
  5615.  
  5616. #include "globdefs.p"
  5617. implementation
  5618. end.
  5619.  
  5620. {!prims.i!}
  5621. {$I COMPILER.INC}
  5622. #include "header.p"
  5623. unit PRIMS;
  5624.  
  5625. interface
  5626.  
  5627. uses GLOBDEFS;
  5628.  
  5629. procedure ERROR(S : String);
  5630. procedure MESSAGE(S : String);
  5631. procedure INITIO;
  5632. function OPEN(NAME : STRINGZ;
  5633.               MODE : Integer) : FILEDESC;
  5634. function CREATE(NAME : STRINGZ;
  5635.                 MODE : Integer) : FILEDESC;
  5636. function GETC(var C : CHARACTER) : CHARACTER;
  5637. function GETCF(var C : CHARACTER;
  5638.                FD : FILEDESC) : CHARACTER;
  5639. function GETLINE(var S : STRINGZ;
  5640.                  FD : FILEDESC;
  5641.                  MAXSIZE : Integer) : Boolean;
  5642. procedure PUTC(C : CHARACTER);
  5643. procedure PUTCF(C : CHARACTER;
  5644.                 FD : FILEDESC);
  5645. procedure PUTSTR(var S : STRINGZ;
  5646.                  FD : FILEDESC);
  5647. procedure CLOSEZ(FD : FILEDESC);
  5648. procedure REMOVE(NAME : STRINGZ);
  5649. function GETARG(N : Integer;
  5650.                 var S : STRINGZ;
  5651.                 MAXSIZE : Integer) : Boolean;
  5652. function NARGS : Integer;
  5653.  
  5654.  
  5655.  
  5656. implementation
  5657.  
  5658.   { TP7 command line argument declarations }
  5659. const
  5660.   MAX_ARGS = 64;
  5661.   NARG : Integer = -1;
  5662. var
  5663.   ARGS : array[1..MAX_ARGS, 1..2] of Integer;
  5664.   CMD_TAIL : String[127];
  5665.  
  5666.  
  5667.   { parse_arg -- parse command line arguments TP7 }
  5668.   procedure PARSE_ARG;
  5669.   {Split the command line tail into arguments using blanks as separators.
  5670.   If an argument requires embedded blanks, surround it with quotation
  5671.   marks (") which will be stripped off retaining the embedded blanks.}
  5672.   const
  5673.     DELIMITER = DQUOTE;
  5674.   type
  5675.     STR_PTR = ^String;
  5676.   var
  5677.     INDEX : Integer;
  5678.     C : CHARACTER;
  5679.     CMD_TAIL_LEN : Integer;     {length of command tail}
  5680.   begin
  5681.     CMD_TAIL := STR_PTR(Ptr(PrefixSeg, $80))^;
  5682.     NARG := 0;
  5683.     for INDEX := 1 to MAX_ARGS do
  5684.       begin
  5685.         ARGS[INDEX, 1] := 0;
  5686.         ARGS[INDEX, 2] := 0;
  5687.       end; { for }
  5688.  
  5689.     CMD_TAIL_LEN := Length(CMD_TAIL);
  5690.     if (CMD_TAIL_LEN <> 0) then
  5691.       begin
  5692.         INDEX := 1;
  5693.         repeat
  5694.           if (Ord(CMD_TAIL[INDEX]) <> BLANK) then
  5695.             begin
  5696.               Inc(NARG);
  5697.  
  5698.               if (Ord(CMD_TAIL[INDEX]) = DELIMITER) then
  5699.                 begin
  5700.                   ARGS[NARG, 1] := INDEX+1;
  5701.                   C := DELIMITER;
  5702.                 end
  5703.               else
  5704.                 begin
  5705.                   ARGS[NARG, 1] := INDEX;
  5706.                   C := BLANK;
  5707.                 end; { if }
  5708.  
  5709.               repeat
  5710.                 Inc(INDEX);
  5711.               until ((Ord(CMD_TAIL[INDEX]) = C) or
  5712.                      (INDEX > CMD_TAIL_LEN));
  5713.               ARGS[NARG, 2] := INDEX-1;
  5714.             end; { if }
  5715.           Inc(INDEX);
  5716.         until (INDEX > CMD_TAIL_LEN);
  5717.       end; { if }
  5718.   end; { procedure }
  5719.  
  5720.  
  5721.  
  5722.  
  5723.   {------------------------------------------------}
  5724.  
  5725. #include "error.p"
  5726. #include "message.p"
  5727. #include "prims.p"
  5728. begin
  5729.   INITIO;
  5730. end.
  5731.  
  5732. {!utility.i!}
  5733. {$I COMPILER.INC}
  5734. #include "header.p"
  5735. unit UTILITY;
  5736.  
  5737. interface
  5738.  
  5739. uses GLOBDEFS;
  5740.  
  5741. function ADDSTR(C : CHARACTER;
  5742.                 var OUTSET : STRINGZ;
  5743.                 var J : Integer;
  5744.                 MAXSET : Integer) : Boolean;
  5745. function EQUAL(var STR1, STR2 : STRINGZ) : Boolean;
  5746. function ESC(var S : STRINGZ;
  5747.              var I : Integer) : CHARACTER;
  5748. function INDEXZ(var S : STRINGZ;
  5749.                 C : CHARACTER) : Integer;
  5750. function ISALPHANUM(C : CHARACTER) : Boolean;
  5751. function ISDIGIT(C : CHARACTER) : Boolean;
  5752. function ISLETTER(C : CHARACTER) : Boolean;
  5753. function ISLOWER(C : CHARACTER) : Boolean;
  5754. function ISUPPER(C : CHARACTER) : Boolean;
  5755. function ITOC(N : Integer;
  5756.               var S : STRINGZ;
  5757.               I : Integer) : Integer;
  5758. function LENGTHZ(var S : STRINGZ) : Integer;
  5759. function MAX(X, Y : Integer) : Integer;
  5760. function MIN(X, Y : Integer) : Integer;
  5761. procedure SCOPY(var SRC : STRINGZ;
  5762.                 I : Integer;
  5763.                 var DEST : STRINGZ;
  5764.                 J : Integer);
  5765. function CTOI(var S : STRINGZ;
  5766.               var I : Integer) : Integer;
  5767. procedure FCOPY(FIN, FOUT : FILEDESC);
  5768. function MUSTCREATE(var NAME : STRINGZ;
  5769.                     MODE : Integer) : FILEDESC;
  5770. function MUSTOPEN(var NAME : STRINGZ;
  5771.                   MODE : Integer) : FILEDESC;
  5772. procedure PUTDEC(N, W : Integer);
  5773.  
  5774.  
  5775.  
  5776. implementation
  5777.  
  5778. uses PRIMS;
  5779.  
  5780. #include "utility.p"
  5781. end.
  5782.  
  5783. {!charcnt.i!}
  5784. {$I COMPILER.INC}
  5785. #include "header.p"
  5786.  
  5787. uses GLOBDEFS, PRIMS, UTILITY;
  5788.  
  5789. #include "charcnt.p"
  5790. begin
  5791.   CHARCOUNT;
  5792. end.
  5793.  
  5794. {!linecnt.i!}
  5795. {$I COMPILER.INC}
  5796. #include "header.p"
  5797. uses GLOBDEFS, PRIMS, UTILITY;
  5798.  
  5799. #include "linecnt.p"
  5800. begin
  5801.   LINECOUNT;
  5802. end.
  5803.  
  5804. {!wordcnt.i!}
  5805. {$I COMPILER.INC}
  5806. #include "header.p"
  5807. uses GLOBDEFS, PRIMS, UTILITY;
  5808.  
  5809. #include "wordcnt.p"
  5810. begin
  5811.   WORDCOUNT;
  5812. end.
  5813.  
  5814. {!detab.i!}
  5815. {$I COMPILER.INC}
  5816. #include "header.p"
  5817. uses GLOBDEFS, PRIMS, UTILITY;
  5818.  
  5819. #include "detab.p"
  5820. begin
  5821.   DETAB;
  5822. end.
  5823.  
  5824. {!entab.i!}
  5825. {$I COMPILER.INC}
  5826. #include "header.p"
  5827. uses GLOBDEFS, PRIMS, UTILITY;
  5828.  
  5829. #include "entab.p"
  5830. begin
  5831.   ENTAB;
  5832. end.
  5833.  
  5834. {!overstrk.i!}
  5835. {$I COMPILER.INC}
  5836. #include "header.p"
  5837. uses GLOBDEFS, PRIMS, UTILITY;
  5838.  
  5839. #include "overstrk.p"
  5840. begin
  5841.   OVERSTRIKE;
  5842. end.
  5843.  
  5844. {!compress.i!}
  5845. {$I COMPILER.INC}
  5846. #include "header.p"
  5847. uses GLOBDEFS, PRIMS, UTILITY;
  5848.  
  5849. #include "compress.p"
  5850. begin
  5851.   COMPRESS;
  5852. end.
  5853.  
  5854. {!expand.i!}
  5855. {$I COMPILER.INC}
  5856. #include "header.p"
  5857. uses GLOBDEFS, PRIMS, UTILITY;
  5858.  
  5859. #include "expand.p"
  5860. begin
  5861.   EXPAND;
  5862. end.
  5863.  
  5864. {!echoz.i!}
  5865. {$I COMPILER.INC}
  5866. #include "header.p"
  5867. uses GLOBDEFS, PRIMS, UTILITY;
  5868.  
  5869. #include "echo.p"
  5870. begin
  5871.   ECHO;
  5872. end.
  5873.  
  5874. {!translit.i!}
  5875. {$I COMPILER.INC}
  5876. #include "header.p"
  5877. uses GLOBDEFS, PRIMS, UTILITY;
  5878.  
  5879. #include "translit.p"
  5880. begin
  5881.   TRANSLIT;
  5882. end.
  5883.  
  5884. {!compare.i!}
  5885. {$I COMPILER.INC}
  5886. #include "header.p"
  5887. uses GLOBDEFS, PRIMS, UTILITY;
  5888.  
  5889. #include "compare.p"
  5890. begin
  5891.   COMPARE;
  5892. end.
  5893.  
  5894. {!include.i!}
  5895. {$I COMPILER.INC}
  5896. #include "header.p"
  5897. uses GLOBDEFS, PRIMS, UTILITY;
  5898.  
  5899. #include "includez.p"
  5900. begin
  5901.   INCLUDEZ;
  5902. end.
  5903.  
  5904. {!concat.i!}
  5905. {$I COMPILER.INC}
  5906. #include "header.p"
  5907. uses GLOBDEFS, PRIMS, UTILITY;
  5908.  
  5909. #include "concatz.p"
  5910. begin
  5911.   CONCATZ;
  5912. end.
  5913.  
  5914. {!printz.i!}
  5915. {$I COMPILER.INC}
  5916. #include "header.p"
  5917. uses GLOBDEFS, PRIMS, UTILITY;
  5918.  
  5919. #include "print.p"
  5920. begin
  5921.   PRINT;
  5922. end.
  5923.  
  5924. {!makecopy.i!}
  5925. {$I COMPILER.INC}
  5926. #include "header.p"
  5927. uses GLOBDEFS, PRIMS, UTILITY;
  5928.  
  5929. #include "makecopy.p"
  5930. begin
  5931.   MAKECOPY;
  5932. end.
  5933.  
  5934. {!archive.i!}
  5935. {$I COMPILER.INC}
  5936. #include "header.p"
  5937. uses GLOBDEFS, PRIMS, UTILITY;
  5938.  
  5939. #include "archive.p"
  5940. begin
  5941.   ARCHIVE;
  5942. end.
  5943.  
  5944. {!sortz.i!}
  5945. {$I COMPILER.INC}
  5946. #include "header.p"
  5947. uses GLOBDEFS, PRIMS, UTILITY;
  5948.  
  5949. #include "sort.p"
  5950. begin
  5951.   SORT;
  5952. end.
  5953.  
  5954. {!unique.i!}
  5955. {$I COMPILER.INC}
  5956. #include "header.p"
  5957. uses GLOBDEFS, PRIMS, UTILITY;
  5958.  
  5959. #include "unique.p"
  5960. begin
  5961.   UNIQUE;
  5962. end.
  5963.  
  5964. {!kwic.i!}
  5965. {$I COMPILER.INC}
  5966. #include "header.p"
  5967. uses GLOBDEFS, PRIMS, UTILITY;
  5968.  
  5969. #include "kwic.p"
  5970. begin
  5971.   KWIC;
  5972. end.
  5973.  
  5974. {!unrotate.i!}
  5975. {$I COMPILER.INC}
  5976. #include "header.p"
  5977. uses GLOBDEFS, PRIMS, UTILITY;
  5978.  
  5979. #include "unrotate.p"
  5980. begin
  5981.   UNROTATE;
  5982. end.
  5983.  
  5984. {!findz.i!}
  5985. {$I COMPILER.INC}
  5986. #include "header.p"
  5987. uses GLOBDEFS, PRIMS, UTILITY;
  5988.  
  5989. #include "find.p"
  5990. begin
  5991.   FIND;
  5992. end.
  5993.  
  5994. {!change.i!}
  5995. {$I COMPILER.INC}
  5996. #include "header.p"
  5997. uses GLOBDEFS, PRIMS, UTILITY;
  5998.  
  5999. #include "change.p"
  6000. begin
  6001.   CHANGE;
  6002. end.
  6003.  
  6004. {!edit1.i!}
  6005. {$I COMPILER.INC}
  6006. #include "header.p"
  6007. {$define INMEM}
  6008.  
  6009. uses GLOBDEFS, PRIMS, UTILITY;
  6010.  
  6011. #include "edit1.p"
  6012. begin
  6013.   EDIT;
  6014. end.
  6015.  
  6016. {!edit2.i!}
  6017. {$I COMPILER.INC}
  6018. #include "header.p"
  6019. {$define INMEM}
  6020.  
  6021. uses GLOBDEFS, PRIMS, UTILITY;
  6022.  
  6023. #include "edit2.p"
  6024. begin
  6025.   EDIT;
  6026. end.
  6027.  
  6028. {!formatz.i!}
  6029. {$I COMPILER.INC}
  6030. #include "header.p"
  6031. uses GLOBDEFS, PRIMS, UTILITY;
  6032.  
  6033. #include "format.p"
  6034. begin
  6035.   FORMAT;
  6036. end.
  6037.  
  6038. {!define.i!}
  6039. {$I COMPILER.INC}
  6040. #include "header.p"
  6041. uses GLOBDEFS, PRIMS, UTILITY;
  6042.  
  6043. #include "define.p"
  6044. begin
  6045.   DEFINE;
  6046. end.
  6047.  
  6048. {!macro.i!}
  6049. {$I COMPILER.INC}
  6050. #include "header.p"
  6051. uses GLOBDEFS, PRIMS, UTILITY;
  6052.  
  6053. #include "macro.p"
  6054. begin
  6055.   MACRO;
  6056. end.
  6057.  
  6058.